mirror of
https://gitee.com/baidu/amis.git
synced 2024-12-03 20:39:07 +08:00
Merge branch 'pre-release' into amis-saas-9193
This commit is contained in:
commit
db0ff4f3e8
@ -1,10 +1,28 @@
|
||||
{
|
||||
"name": "amis-editor-core",
|
||||
"version": "5.2.4-beta.2",
|
||||
"version": "5.2.4-beta.32",
|
||||
"description": "amis 可视化编辑器",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": "./lib/index.js",
|
||||
"import": "./esm/index.js"
|
||||
},
|
||||
"./lib/*": {
|
||||
"require": "./lib/*",
|
||||
"import": "./esm/*"
|
||||
},
|
||||
"./scss/*": {
|
||||
"require": "./scss/*",
|
||||
"import": "./scss/*"
|
||||
},
|
||||
"./*": {
|
||||
"require": "./lib/*.js",
|
||||
"import": "./esm/*.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "npm run clean-dist && NODE_ENV=production rollup -c",
|
||||
|
57
packages/amis-editor-core/scss/control/_status.scss
Normal file
57
packages/amis-editor-core/scss/control/_status.scss
Normal file
@ -0,0 +1,57 @@
|
||||
// 状态组件页面设计器
|
||||
.ae-Status-label {
|
||||
& > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&-tip-icon {
|
||||
margin-left: 0.25rem;
|
||||
line-height: 16px;
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: #84868c;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ae-Status-default-icon {
|
||||
&-tooltip {
|
||||
max-width: none;
|
||||
}
|
||||
&-tip {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.ae-Status-control {
|
||||
.cxd-Combo-itemInner {
|
||||
.cxd-Form-row {
|
||||
margin-right: 0;
|
||||
.cxd-Form-col {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
&:first-child {
|
||||
flex-grow: 0;
|
||||
min-width: 55px;
|
||||
.cxd-IconSelectControl-input {
|
||||
padding-left: 6px;
|
||||
}
|
||||
.cxd-IconSelectControl-clear {
|
||||
right: 4px;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
flex-grow: 0;
|
||||
}
|
||||
.cxd-IconSelectControl-input-icon-id {
|
||||
display: none;
|
||||
}
|
||||
.Theme-ColorPicker-label-out {
|
||||
width: 1.35rem;
|
||||
height: 1.35rem;
|
||||
position: relative;
|
||||
top: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@
|
||||
&-input-label {
|
||||
flex: 1;
|
||||
margin-right: var(--gap-sm);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&-input-value {
|
||||
margin-right: var(--gap-sm);
|
||||
|
@ -35,6 +35,7 @@
|
||||
@import './control/timeline_item_control';
|
||||
@import './control/tree_option_control';
|
||||
@import './control/_inpupt-file';
|
||||
@import './control/_status';
|
||||
|
||||
/* 样式控件 */
|
||||
@import './style-control/box-model';
|
||||
@ -482,41 +483,94 @@
|
||||
background-color: #fff;
|
||||
transform: translate(-50%, -50%);
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
pointer-events: all;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ae-WResizer {
|
||||
left: 100%;
|
||||
top: 50%;
|
||||
cursor: ew-resize;
|
||||
&[data-value]::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: content-box;
|
||||
border: 6px solid rgba(7, 12, 20, 0.85);
|
||||
border-color: transparent transparent transparent rgba(7, 12, 20, 0.85);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
&[data-value]:after {
|
||||
position: absolute;
|
||||
content: attr(data-value);
|
||||
background-color: $Editor-theme;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
padding: 2px 8px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background-color: rgba(7,12,20,0.85);
|
||||
// background-color: $Editor-theme;
|
||||
box-shadow: 0 2px 8px 0 rgba(7,12,20,0.12);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ae-border-WResizer,
|
||||
.ae-border-HResizer {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: -2px;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
border-right: 2px solid $Editor-theme;
|
||||
background-color: #fff;
|
||||
z-index: 99;
|
||||
pointer-events: all;
|
||||
cursor: ew-resize;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ae-border-HResizer {
|
||||
top: auto;
|
||||
bottom: -2px;
|
||||
right: 0px;
|
||||
border-right: none;
|
||||
border-bottom: 2px solid $Editor-theme;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
cursor: ns-resize !important;
|
||||
}
|
||||
|
||||
.ae-WResizer {
|
||||
left: calc(100% + 1px);
|
||||
top: 50%;
|
||||
cursor: ew-resize;
|
||||
|
||||
&[data-value]:after {
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.ae-HResizer {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
top: calc(100% + 1px);
|
||||
cursor: ns-resize;
|
||||
|
||||
&[data-value]::before {
|
||||
right: 50%;
|
||||
top: auto;
|
||||
bottom: 8px;
|
||||
transform: translate(50%, 0);
|
||||
border-color: rgba(7, 12, 20, 0.85) transparent transparent transparent;
|
||||
}
|
||||
|
||||
&[data-value]:after {
|
||||
position: absolute;
|
||||
content: attr(data-value);
|
||||
background-color: $Editor-theme;
|
||||
bottom: 15px;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
padding: 2px 8px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@ -525,15 +579,13 @@
|
||||
top: 100%;
|
||||
cursor: nwse-resize;
|
||||
|
||||
&[data-value]::before {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&[data-value]:after {
|
||||
position: absolute;
|
||||
content: attr(data-value);
|
||||
background-color: $Editor-theme;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
padding: 2px 8px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,7 +616,9 @@
|
||||
|
||||
.ae-WResizer,
|
||||
.ae-HResizer,
|
||||
.ae-Resizer {
|
||||
.ae-Resizer,
|
||||
.ae-border-WResizer,
|
||||
.ae-border-HResizer {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@ -574,6 +628,17 @@
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
&.isFreeContainerElem {
|
||||
// 自由容器的直接子元素拖拽剔除过渡
|
||||
transition: none !important;
|
||||
|
||||
|
||||
&.selected {
|
||||
pointer-events: all; // 高亮区启用鼠标事件,支持拖拽
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
.ae-Editor-toolbar {
|
||||
display: none;
|
||||
background: $Editor-hlbox-bg;
|
||||
@ -1111,6 +1176,14 @@
|
||||
background: #f7f7f8;
|
||||
}
|
||||
|
||||
&.fill-placeholder {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: calc(100% - 8px) !important;
|
||||
height: calc(100% - 8px) !important;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -1362,7 +1435,7 @@ div.ae-DragImage {
|
||||
white-space: nowrap;
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
z-index: 100000;
|
||||
z-index: -10;
|
||||
|
||||
> span {
|
||||
position: absolute;
|
||||
|
@ -40,7 +40,6 @@
|
||||
}
|
||||
}
|
||||
.Theme-ShadowEditor {
|
||||
margin-left: -0.5rem;
|
||||
.Theme-Wrapper-header-left {
|
||||
font-weight: 400;
|
||||
color: #5c5f66;
|
||||
|
@ -17,6 +17,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;
|
||||
@ -98,6 +99,11 @@ export interface EditorProps extends PluginEventListener {
|
||||
};
|
||||
};
|
||||
|
||||
/** 上下文变量 */
|
||||
variables?: VariableGroup[];
|
||||
/** 变量配置 */
|
||||
variableOptions?: VariableOptions;
|
||||
|
||||
onUndo?: () => void; // 用于触发外部 undo 事件
|
||||
onRedo?: () => void; // 用于触发外部 redo 事件
|
||||
onSave?: () => void; // 用于触发外部 save 事件
|
||||
@ -139,7 +145,10 @@ export default class Editor extends Component<EditorProps> {
|
||||
isSubEditor,
|
||||
amisDocHost: props.amisDocHost,
|
||||
ctx: props.ctx,
|
||||
superEditorData
|
||||
superEditorData,
|
||||
appLocale: props.appLocale,
|
||||
appCorpusData: props?.amisEnv?.replaceText,
|
||||
i18nEnabled: props?.i18nEnabled ?? false
|
||||
},
|
||||
config
|
||||
);
|
||||
@ -149,7 +158,10 @@ export default class Editor extends Component<EditorProps> {
|
||||
}
|
||||
this.manager = new EditorManager(config, this.store);
|
||||
|
||||
(window as any).editorStore = this.store;
|
||||
// 子编辑器不再重新设置 editorStore
|
||||
if (!(props.isSubEditor && (window as any).editorStore)) {
|
||||
(window as any).editorStore = this.store;
|
||||
}
|
||||
|
||||
// 添加快捷键事件
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
@ -212,6 +224,14 @@ export default class Editor extends Component<EditorProps> {
|
||||
if (props.ctx !== prevProps.ctx) {
|
||||
this.store.setCtx(props.ctx);
|
||||
}
|
||||
|
||||
if (props.appLocale !== prevProps.appLocale) {
|
||||
this.store.setAppLocale(props.appLocale);
|
||||
}
|
||||
|
||||
if (props?.amisEnv?.replaceText !== prevProps?.amisEnv?.replaceText) {
|
||||
this.store.setAppCorpusData(props?.amisEnv?.replaceText);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -543,6 +563,8 @@ export default class Editor extends Component<EditorProps> {
|
||||
store={this.store}
|
||||
manager={this.manager}
|
||||
theme={theme}
|
||||
appLocale={appLocale}
|
||||
amisEnv={amisEnv}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -48,7 +48,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
if (!isLeftButton || e.defaultPrevented) return;
|
||||
|
||||
e.preventDefault();
|
||||
const {manager, id, node} = this.props;
|
||||
const {manager, id, node, store} = this.props;
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
@ -69,6 +69,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
](e, {
|
||||
dom: target as HTMLElement,
|
||||
node: node,
|
||||
store: store,
|
||||
resizer:
|
||||
direction === 'both'
|
||||
? this.resizerDom
|
||||
@ -136,7 +137,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
if (ref) {
|
||||
ref.addEventListener('mousedown', this.handleWResizerMouseDown);
|
||||
} else {
|
||||
this.wResizerDom.addEventListener(
|
||||
this.wResizerDom?.addEventListener(
|
||||
'mousedown',
|
||||
this.handleWResizerMouseDown
|
||||
);
|
||||
@ -152,7 +153,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
if (ref) {
|
||||
ref.addEventListener('mousedown', this.handleHResizerMouseDown);
|
||||
} else {
|
||||
this.hResizerDom.addEventListener(
|
||||
this.hResizerDom?.addEventListener(
|
||||
'mousedown',
|
||||
this.handleHResizerMouseDown
|
||||
);
|
||||
@ -168,7 +169,7 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
if (ref) {
|
||||
ref.addEventListener('mousedown', this.handleResizerMouseDown);
|
||||
} else {
|
||||
this.resizerDom.addEventListener(
|
||||
this.resizerDom?.addEventListener(
|
||||
'mousedown',
|
||||
this.handleResizerMouseDown
|
||||
);
|
||||
@ -188,6 +189,18 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
this.props.store.setHoverId(this.props.id);
|
||||
}
|
||||
|
||||
// 特殊布局元素和自由容器直接子元素直接拖拽调整位置
|
||||
@autobind
|
||||
handleDragStart(e: React.DragEvent) {
|
||||
const {manager, id} = this.props;
|
||||
|
||||
if (manager.disableHover) {
|
||||
return;
|
||||
}
|
||||
|
||||
manager.startDrag(id, e);
|
||||
}
|
||||
|
||||
// @autobind
|
||||
// handleMouseLeave() {
|
||||
// this.props.store.setHoverId(this.props.id);
|
||||
@ -208,8 +221,10 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
const secondaryToolbars = store.sortedSecondaryToolbars;
|
||||
const specialToolbars = store.sortedSpecialToolbars;
|
||||
const isActive = store.isActive(id);
|
||||
const curFreeContainerId = store.parentIsFreeContainer();
|
||||
const isHover =
|
||||
store.isHoved(id) || store.dropId === id || store.insertOrigId === id;
|
||||
store.isHoved(id) || store.dropId === id || store.insertOrigId === id || curFreeContainerId === id;
|
||||
const isDraggableContainer = store.draggableContainer(id);
|
||||
|
||||
// 获取当前高亮画布宽度
|
||||
const aePreviewOffsetWidth = document.getElementById(
|
||||
@ -228,7 +243,8 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
hover: isHover,
|
||||
regionOn: node.childRegions.some(region =>
|
||||
store.isRegionHighlighted(region.id, region.region)
|
||||
)
|
||||
),
|
||||
isFreeContainerElem: !!curFreeContainerId || isDraggableContainer
|
||||
},
|
||||
className
|
||||
)}
|
||||
@ -242,6 +258,8 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
}}
|
||||
ref={this.mainRef}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
draggable={!!curFreeContainerId || isDraggableContainer}
|
||||
onDragStart={this.handleDragStart}
|
||||
>
|
||||
{isActive ? (
|
||||
<div
|
||||
@ -352,11 +370,17 @@ export default class HighlightBox extends React.Component<HighlightBoxProps> {
|
||||
{children}
|
||||
|
||||
{node.widthMutable ? (
|
||||
<span className="ae-WResizer" ref={this.wResizerRef}></span>
|
||||
<>
|
||||
<span className="ae-border-WResizer" ref={this.wResizerRef}></span>
|
||||
<span className="ae-WResizer" ref={this.wResizerRef}></span>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{node.heightMutable ? (
|
||||
<span className="ae-HResizer" ref={this.hResizerRef}></span>
|
||||
<>
|
||||
<span className="ae-border-HResizer" ref={this.hResizerRef}></span>
|
||||
<span className="ae-HResizer" ref={this.hResizerRef}></span>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{node.widthMutable && node.heightMutable ? (
|
||||
|
@ -48,10 +48,20 @@ export default class RenderersPanel extends React.Component<
|
||||
manager.switchToRegion(region);
|
||||
}
|
||||
|
||||
handleDragStart(e: React.DragEvent) {
|
||||
@autobind
|
||||
handleDragStart(e: React.DragEvent, label: string) {
|
||||
const current = e.currentTarget;
|
||||
const id = current.getAttribute('data-id')!;
|
||||
e.dataTransfer.setData(`dnd-dom/[data-id="${id}"]`, '');
|
||||
/*
|
||||
// 增加默认拖拽过程中元素
|
||||
e.dataTransfer!.effectAllowed = 'move';
|
||||
e.dataTransfer!.setDragImage(
|
||||
this.props.manager?.dnd?.createDragImage(label),
|
||||
0,
|
||||
0
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
||||
// 组件点选使用
|
||||
@ -175,7 +185,7 @@ export default class RenderersPanel extends React.Component<
|
||||
data-dnd-data={JSON.stringify(
|
||||
item.scaffold || {type: item.type}
|
||||
)}
|
||||
onDragStart={this.handleDragStart}
|
||||
onDragStart={(e: React.DragEvent) => this.handleDragStart(e, item.name)}
|
||||
>
|
||||
<div
|
||||
className="icon-box"
|
||||
|
@ -14,6 +14,8 @@ interface RightPanelsProps {
|
||||
store: EditorStoreType;
|
||||
manager: EditorManager;
|
||||
theme?: string;
|
||||
appLocale?: string;
|
||||
amisEnv?: any;
|
||||
}
|
||||
|
||||
interface RightPanelsStates {
|
||||
|
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import {EditorManager} from '../manager';
|
||||
import {RegionConfig, RendererInfo} from '../plugin';
|
||||
import {needFillPlaceholder} from '../util';
|
||||
import {EditorStoreType} from '../store/editor';
|
||||
import {EditorNodeContext, EditorNodeType} from '../store/node';
|
||||
|
||||
@ -105,12 +106,18 @@ export class RegionWrapper extends React.Component<RegionWrapperProps> {
|
||||
const isLayoutItem =
|
||||
this.props.rendererName === 'wrapper' ||
|
||||
this.props.rendererName === 'container';
|
||||
let isNeedFillPlaceholder = false;
|
||||
if (needFillPlaceholder(this.props)) {
|
||||
isNeedFillPlaceholder = true;
|
||||
}
|
||||
return (
|
||||
<EditorNodeContext.Provider value={this.editorNode}>
|
||||
{this.props.children}
|
||||
<span
|
||||
className={`ae-Region-placeholder ${
|
||||
isLayoutItem ? 'layout-content' : ''
|
||||
} ${
|
||||
isNeedFillPlaceholder ? 'fill-placeholder' : ''
|
||||
}`}
|
||||
>
|
||||
{this.props.placeholder || this.props.label}
|
||||
|
@ -104,7 +104,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
|
||||
if (!!subEditorContext) {
|
||||
superEditorData = createObject(
|
||||
store.superEditorData,
|
||||
subEditorContext?.data?.__super
|
||||
subEditorContext?.data
|
||||
);
|
||||
}
|
||||
return {
|
||||
@ -157,6 +157,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
|
||||
appLocale={config.appLocale}
|
||||
i18nEnabled={config.i18nEnabled}
|
||||
plugins={config.plugins}
|
||||
actionOptions={config.actionOptions}
|
||||
showCustomRenderersPanel={
|
||||
store.showCustomRenderersPanel ?? true
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import {Schema} from 'amis';
|
||||
import type {DataScope} from 'amis-core';
|
||||
import type {RendererConfig} from 'amis-core/lib/factory';
|
||||
import {SchemaCollection} from 'amis/lib/Schema';
|
||||
import {omit} from 'lodash';
|
||||
|
||||
// 创建 Node Store 并构建成树
|
||||
export function makeWrapper(
|
||||
@ -302,7 +303,7 @@ function SchemaFrom({
|
||||
popOverContainer
|
||||
},
|
||||
{
|
||||
...env
|
||||
...omit(env, 'replaceText')
|
||||
// theme: 'cxd' // 右侧属性配置面板固定使用cxd主题展示
|
||||
}
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import findIndex from 'lodash/findIndex';
|
||||
import {EditorDNDManager} from '.';
|
||||
import {renderThumbToGhost} from '../component/factory';
|
||||
import {EditorNodeType} from '../store/node';
|
||||
import {translateSchema} from '../util';
|
||||
import {DNDModeInterface} from './interface';
|
||||
|
||||
export class DefaultDNDMode implements DNDModeInterface {
|
||||
@ -56,7 +57,12 @@ export class DefaultDNDMode implements DNDModeInterface {
|
||||
} else {
|
||||
const manager = this.dnd.manager;
|
||||
const store = manager.store;
|
||||
renderThumbToGhost(ghost, this.region, store.dragSchema, manager);
|
||||
renderThumbToGhost(
|
||||
ghost,
|
||||
this.region,
|
||||
translateSchema(store.dragSchema),
|
||||
manager
|
||||
);
|
||||
this.dndContainer.appendChild(ghost);
|
||||
}
|
||||
}
|
||||
|
@ -94,11 +94,11 @@ export class EditorDNDManager {
|
||||
* @param id
|
||||
* @param node
|
||||
*/
|
||||
createDragImage(id: string, node: EditorNodeType) {
|
||||
createDragImage(id: string, node?: EditorNodeType) {
|
||||
const dragImage = document.createElement('div');
|
||||
dragImage.classList.add('ae-DragImage');
|
||||
// bca-disable-next-line
|
||||
dragImage.innerHTML = `<span>${node.label}</span>`;
|
||||
dragImage.innerHTML = `<span>${node?.label || id}</span>`;
|
||||
document.body.appendChild(dragImage);
|
||||
// dragImage.style.cssText += `width: ${node.w}px; height: ${node.h}px;`;
|
||||
this.dragImage = dragImage;
|
||||
@ -296,9 +296,15 @@ export class EditorDNDManager {
|
||||
// 如果当前选中了某个组件,则默认让其第一个区域处于拖入状态。
|
||||
if (containerId) {
|
||||
const node = store.getNodeById(containerId);
|
||||
|
||||
if (node?.childRegions.length) {
|
||||
this.switchToRegion(e, node.id, node.childRegions[0].region);
|
||||
let slotIndex = 0;
|
||||
node.childRegions.forEach((regionItem: any, index: number) => {
|
||||
// 优先使用body作为插入子元素的位置
|
||||
if (regionItem.region) {
|
||||
slotIndex = index;
|
||||
}
|
||||
});
|
||||
this.switchToRegion(e, node.id, node.childRegions[slotIndex].region);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -325,9 +331,9 @@ export class EditorDNDManager {
|
||||
const curElem = target.closest(`[data-region][data-region-host]`);
|
||||
const hostId = curElem?.getAttribute('data-region-host');
|
||||
const region = curElem?.getAttribute('data-region');
|
||||
|
||||
|
||||
if (
|
||||
d > 5 &&
|
||||
d > 0 &&
|
||||
this.curDragId &&
|
||||
this.manager.draggableContainer(this.curDragId)
|
||||
) {
|
||||
|
@ -261,5 +261,6 @@ extendLocale('en-US', {
|
||||
'2a6287664de5ab46c65220c7182878ff': 'Cut',
|
||||
'db805d4e361ac2d3fc6047eaea1a7c69': 'Free container',
|
||||
'969e9e56b3812abffa3994f35ea31835': 'Adsorption vessel',
|
||||
'5b804b05e14aaacc75033a4f77fc2844': 'Source code'
|
||||
'5b804b05e14aaacc75033a4f77fc2844': 'Source code',
|
||||
'69a53230577258b6d97ee932befcc168': '<Column>:'
|
||||
});
|
||||
|
@ -233,5 +233,6 @@ extendLocale('zh-CN', {
|
||||
'2a6287664de5ab46c65220c7182878ff': '已剪切',
|
||||
'db805d4e361ac2d3fc6047eaea1a7c69': '自由容器',
|
||||
'969e9e56b3812abffa3994f35ea31835': '吸附容器',
|
||||
'5b804b05e14aaacc75033a4f77fc2844': '源码'
|
||||
'5b804b05e14aaacc75033a4f77fc2844': '源码',
|
||||
'69a53230577258b6d97ee932befcc168': '<列>:'
|
||||
});
|
||||
|
@ -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,
|
||||
@ -55,16 +55,21 @@ 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';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
import {omit} from 'lodash';
|
||||
|
||||
export interface EditorManagerConfig
|
||||
extends Omit<EditorProps, 'value' | 'onChange'> {}
|
||||
@ -158,6 +163,9 @@ export class EditorManager {
|
||||
dataSchema: DataSchema;
|
||||
readonly isInFrame: boolean = false;
|
||||
|
||||
/** 变量管理 */
|
||||
readonly variableManager;
|
||||
|
||||
constructor(
|
||||
readonly config: EditorManagerConfig,
|
||||
readonly store: EditorStoreType,
|
||||
@ -168,7 +176,7 @@ export class EditorManager {
|
||||
// 传给 amis 渲染器的默认 env
|
||||
this.env = {
|
||||
...env, // 默认的 env 中带 jumpTo
|
||||
...config.amisEnv, // 用户也可以设置自定义环境配置,用于覆盖默认的 env
|
||||
...omit(config.amisEnv, 'replaceText'), // 用户也可以设置自定义环境配置,用于覆盖默认的 env
|
||||
theme: config.theme
|
||||
};
|
||||
this.env.beforeDispatchEvent = this.beforeDispatchEvent.bind(
|
||||
@ -207,8 +215,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;
|
||||
}
|
||||
@ -469,6 +484,10 @@ export class EditorManager {
|
||||
let id = curRendererId || this.store.activeId;
|
||||
let panels: Array<BasicPanelItem> = [];
|
||||
|
||||
if (!id && this.store?.schema) {
|
||||
id = this.store?.schema.$$id; // 默认使用根节点id
|
||||
}
|
||||
|
||||
if (id || this.store.selections.length) {
|
||||
id = id || this.store.selections[0];
|
||||
const node = this.store.getNodeById(id);
|
||||
@ -793,7 +812,8 @@ export class EditorManager {
|
||||
if (
|
||||
(node.type === 'wrapper' || node.type === 'container') &&
|
||||
node.schema?.body?.length === 0 &&
|
||||
(schemaData?.type === 'flex' || subRenderer?.rendererName === 'flex')
|
||||
(schemaData?.type === 'flex' || subRenderer?.rendererName === 'flex') &&
|
||||
!node.schema?.isFreeContainer
|
||||
) {
|
||||
const newSchemaData = schemaData || subRenderer?.scaffold;
|
||||
// 布局能力提升: 点击插入新元素,当wrapper为空插入布局容器时,自动改为置换,避免过多层级
|
||||
@ -1033,6 +1053,13 @@ export class EditorManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前元素定位是否为flex容器
|
||||
*/
|
||||
isFlexContainer(id: string) {
|
||||
return this.store.isFlexContainer(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前元素是否为flex布局子容器
|
||||
* 备注: 以便额外增加布局相关配置项
|
||||
@ -1707,6 +1734,7 @@ export class EditorManager {
|
||||
dom: HTMLElement;
|
||||
node: EditorNodeType;
|
||||
resizer: HTMLElement;
|
||||
store: EditorStoreType;
|
||||
}
|
||||
) {
|
||||
return this.trigger('size-change-start', {
|
||||
|
@ -564,6 +564,7 @@ export interface ResizeMoveEventContext extends EventContext {
|
||||
dom: HTMLElement;
|
||||
resizer: HTMLElement;
|
||||
node: EditorNodeType;
|
||||
store: EditorStoreType;
|
||||
}
|
||||
|
||||
export interface AfterBuildPanelBody extends EventContext {
|
||||
@ -625,7 +626,6 @@ export function createEvent<T extends EventContext>(
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
export interface PluginEventListener {
|
||||
onInit?: (
|
||||
event: PluginEvent<
|
||||
@ -1208,3 +1208,146 @@ export abstract class BasePlugin implements PluginInterface {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 布局相关组件基类,带宽高可拖拽功能。
|
||||
*/
|
||||
export class LayoutBasePlugin extends BasePlugin {
|
||||
|
||||
onActive(event: PluginEvent<ActiveEventContext>) {
|
||||
const context = event.context;
|
||||
|
||||
if (context.info?.plugin !== this || !context.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = context.node!;
|
||||
const curSchema = node.schema || {};
|
||||
|
||||
if (curSchema.isFixedWidth) {
|
||||
node.setWidthMutable(true);
|
||||
}
|
||||
if (curSchema.isFixedHeight) {
|
||||
node.setHeightMutable(true);
|
||||
}
|
||||
}
|
||||
|
||||
onWidthChangeStart(
|
||||
event: PluginEvent<
|
||||
ResizeMoveEventContext,
|
||||
{
|
||||
onMove(e: MouseEvent): void;
|
||||
onEnd(e: MouseEvent): void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
return this.onSizeChangeStart(event, 'horizontal');
|
||||
}
|
||||
|
||||
onHeightChangeStart(
|
||||
event: PluginEvent<
|
||||
ResizeMoveEventContext,
|
||||
{
|
||||
onMove(e: MouseEvent): void;
|
||||
onEnd(e: MouseEvent): void;
|
||||
}
|
||||
>
|
||||
) {
|
||||
return this.onSizeChangeStart(event, 'vertical');
|
||||
}
|
||||
|
||||
onSizeChangeStart(
|
||||
event: PluginEvent<
|
||||
ResizeMoveEventContext,
|
||||
{
|
||||
onMove(e: MouseEvent): void;
|
||||
onEnd(e: MouseEvent): void;
|
||||
}
|
||||
>,
|
||||
direction: 'both' | 'vertical' | 'horizontal' = 'both'
|
||||
) {
|
||||
const context = event.context;
|
||||
const node = context.node;
|
||||
const store = context.store;
|
||||
if (node.info?.plugin !== this) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resizer = context.resizer;
|
||||
const dom = context.dom;
|
||||
const doc = store.getDoc() || document;
|
||||
const frameRect = dom.parentElement!.getBoundingClientRect();
|
||||
const rect = dom.getBoundingClientRect();
|
||||
const startX = context.nativeEvent.pageX;
|
||||
const startY = context.nativeEvent.pageY;
|
||||
|
||||
event.setData({
|
||||
onMove: (e: MouseEvent) => {
|
||||
const dy = e.pageY - startY;
|
||||
const dx = e.pageX - startX;
|
||||
const height = Math.max(50, rect.height + dy);
|
||||
const width = Math.max(100, Math.min(rect.width + dx, frameRect.width));
|
||||
const state: any = {
|
||||
width,
|
||||
height
|
||||
};
|
||||
|
||||
const dragHlBoxItem = doc.querySelector(
|
||||
`[data-hlbox-id='${node.id}']`
|
||||
) as HTMLElement;
|
||||
|
||||
// 实时调整被拖拽元素的坐标值
|
||||
const dragContainerItem = doc.querySelector(
|
||||
`[data-editor-id='${node.id}']`
|
||||
) as HTMLElement;
|
||||
|
||||
if (direction === 'both') {
|
||||
resizer.setAttribute('data-value', `${width}px x ${height}px`);
|
||||
dragHlBoxItem.style.height = `${height}px`;
|
||||
dragHlBoxItem.style.width = `${width}px`;
|
||||
dragContainerItem.style.height = `${height}px`;
|
||||
dragContainerItem.style.width = `${width}px`;
|
||||
} else if (direction === 'vertical') {
|
||||
resizer.setAttribute('data-value', `${height}px`);
|
||||
dragHlBoxItem.style.height = `${height}px`;
|
||||
dragContainerItem.style.height = `${height}px`;
|
||||
delete state.width;
|
||||
} else {
|
||||
resizer.setAttribute('data-value', `${width}px`);
|
||||
dragHlBoxItem.style.width = `${width}px`;
|
||||
dragContainerItem.style.width = `${width}px`;
|
||||
delete state.height;
|
||||
}
|
||||
|
||||
node.updateState(state);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
node.calculateHighlightBox();
|
||||
});
|
||||
},
|
||||
onEnd: (e: MouseEvent) => {
|
||||
const dy = e.pageY - startY;
|
||||
const dx = e.pageX - startX;
|
||||
const height = Math.max(50, rect.height + dy);
|
||||
const width = Math.max(100, Math.min(rect.width + dx, frameRect.width));
|
||||
const state: any = {
|
||||
width,
|
||||
height
|
||||
};
|
||||
|
||||
if (direction === 'vertical') {
|
||||
delete state.width;
|
||||
} else if (direction === 'horizontal') {
|
||||
delete state.height;
|
||||
}
|
||||
|
||||
resizer.removeAttribute('data-value');
|
||||
node.updateSchemaStyle(state);
|
||||
requestAnimationFrame(() => {
|
||||
node.calculateHighlightBox();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,4 +1,10 @@
|
||||
import {findTree, getVariable, mapObject, createObject} from 'amis-core';
|
||||
import {
|
||||
findTree,
|
||||
getVariable,
|
||||
mapObject,
|
||||
mapTree,
|
||||
extendObject
|
||||
} from 'amis-core';
|
||||
import {cast, getEnv, Instance, types} from 'mobx-state-tree';
|
||||
import {
|
||||
diff,
|
||||
@ -11,8 +17,8 @@ import {
|
||||
stringRegExp,
|
||||
needDefaultWidth,
|
||||
guid,
|
||||
reGenerateID,
|
||||
addStyleClassName
|
||||
addStyleClassName,
|
||||
appTranslate
|
||||
} from '../../src/util';
|
||||
import {
|
||||
InsertEventContext,
|
||||
@ -44,7 +50,6 @@ import isPlainObject from 'lodash/isPlainObject';
|
||||
import {EditorManagerConfig} from '../manager';
|
||||
import {EditorNode, EditorNodeType} from './node';
|
||||
import findIndex from 'lodash/findIndex';
|
||||
import {cloneDeep} from 'lodash';
|
||||
|
||||
export interface SchemaHistory {
|
||||
versionId: number;
|
||||
@ -201,7 +206,15 @@ export const MainStore = types
|
||||
// 自动收集可以供 target/reload 使用的名称列表
|
||||
targetNames: types.optional(types.array(types.frozen<TargetName>()), []),
|
||||
|
||||
ctx: types.frozen()
|
||||
ctx: types.frozen(),
|
||||
/** 是否开启应用多语言 */
|
||||
i18nEnabled: types.optional(types.boolean, false),
|
||||
/** 应用语言 */
|
||||
appLocale: types.optional(types.string, 'zh-CN'),
|
||||
/** 应用语料 */
|
||||
appCorpusData: types.optional(types.frozen(), {}),
|
||||
/** 应用多语言状态,用于其它组件进行订阅 */
|
||||
appLocaleState: types.optional(types.number, 0)
|
||||
})
|
||||
.views(self => {
|
||||
return {
|
||||
@ -322,6 +335,12 @@ export const MainStore = types
|
||||
: nodes.push.apply(nodes, self.selections);
|
||||
}
|
||||
|
||||
// 判断父元素是否为自由容器元素
|
||||
const curFreeContainerId = this.parentIsFreeContainer(self.activeId);
|
||||
if (curFreeContainerId) {
|
||||
nodes.push(curFreeContainerId);
|
||||
}
|
||||
|
||||
if (self.insertMode === 'insert' && self.insertId) {
|
||||
nodes.push(self.insertId);
|
||||
}
|
||||
@ -865,9 +884,21 @@ export const MainStore = types
|
||||
);
|
||||
return idx < self.schemaHistory.length - 1;
|
||||
},
|
||||
// 判断当前元素定位是否为flex容器
|
||||
isFlexContainer(id: string) {
|
||||
const activeId = id ?? self.activeId;
|
||||
const curSchema = this.getSchema(activeId);
|
||||
if (
|
||||
curSchema?.style?.display === 'flex' ||
|
||||
curSchema?.style?.display === 'inline-flex'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// 判断是否是布局容器中的列级元素
|
||||
isFlexItem(id: string) {
|
||||
const activeId = id || self.activeId;
|
||||
const activeId = id ?? self.activeId;
|
||||
const parentSchema = this.getSchemaParentById(activeId, true);
|
||||
if (
|
||||
parentSchema?.type === 'flex' ||
|
||||
@ -880,7 +911,7 @@ export const MainStore = types
|
||||
},
|
||||
// 判断父级布局容器是否为垂直排列
|
||||
isFlexColumnItem(id: string) {
|
||||
const activeId = id || self.activeId;
|
||||
const activeId = id ?? self.activeId;
|
||||
const parentSchema = this.getSchemaParentById(activeId, true);
|
||||
const isFlexItem =
|
||||
parentSchema?.type === 'flex' ||
|
||||
@ -909,8 +940,59 @@ export const MainStore = types
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// 判断父元素是否为自由容器元素,如果父级元素是自由容器则返回父元素ID
|
||||
parentIsFreeContainer(id?: string) {
|
||||
const activeId = id ?? self.activeId;
|
||||
const curNode = this.getNodeById(activeId)!;
|
||||
const parentNode = curNode?.parent;
|
||||
if (!parentNode) {
|
||||
return false;
|
||||
}
|
||||
const curSchema = this.getSchema(parentNode.id);
|
||||
if (curSchema?.isFreeContainer) {
|
||||
return parentNode.id;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
get getSuperEditorData() {
|
||||
return self.superEditorData || {};
|
||||
},
|
||||
// 获取组件选择树
|
||||
getComponentTreeSource() {
|
||||
return mapTree(
|
||||
self.root.children ?? [],
|
||||
(item: any) => {
|
||||
const schema = item.id
|
||||
? JSONGetById(self.schema, item.id)
|
||||
: self.schema;
|
||||
let cmptLabel = '';
|
||||
const itemLabel = appTranslate(item?.label);
|
||||
const schemaLabel = appTranslate(schema?.label);
|
||||
const schemaTitle = appTranslate(schema?.title);
|
||||
if (item?.region) {
|
||||
cmptLabel = itemLabel;
|
||||
} else {
|
||||
const labelPrefix =
|
||||
item.type !== 'cell' ? `<${itemLabel}>:` : `<列>:`;
|
||||
cmptLabel = `${labelPrefix}${
|
||||
schemaLabel ?? schemaTitle ?? itemLabel
|
||||
}`;
|
||||
}
|
||||
cmptLabel = cmptLabel ?? itemLabel;
|
||||
return {
|
||||
id: item.id,
|
||||
label: cmptLabel,
|
||||
value: schema?.id ?? item.id,
|
||||
type: schema?.type ?? item.type,
|
||||
schema,
|
||||
disabled: !!item.region,
|
||||
visible: item.region ? !!item?.children.length : true,
|
||||
children: item?.children
|
||||
};
|
||||
},
|
||||
1,
|
||||
true
|
||||
);
|
||||
}
|
||||
};
|
||||
})
|
||||
@ -1112,7 +1194,6 @@ export const MainStore = types
|
||||
self.activeId = id;
|
||||
self.activeRegion = region;
|
||||
self.selections = selections;
|
||||
|
||||
// if (!self.panelKey && id) {
|
||||
// self.panelKey = 'config';
|
||||
// }
|
||||
@ -1499,7 +1580,13 @@ export const MainStore = types
|
||||
}
|
||||
self.subEditorContext = {
|
||||
...context,
|
||||
data: context.data || {}
|
||||
data: extendObject(context.data, {
|
||||
__curCmptTreeWrap: {
|
||||
label: context.title,
|
||||
disabled: true
|
||||
},
|
||||
__superCmptTreeSource: self.getComponentTreeSource()
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
@ -1735,6 +1822,26 @@ export const MainStore = types
|
||||
}
|
||||
});
|
||||
this.traceableSetSchema(json);
|
||||
},
|
||||
|
||||
/** 更改应用多语言的状态 */
|
||||
updateAppLocaleState() {
|
||||
self.appLocaleState += 1;
|
||||
},
|
||||
|
||||
/** 设置应用语言,支持应用国际化 */
|
||||
setAppLocale(locale?: string) {
|
||||
if (!locale) {
|
||||
return;
|
||||
}
|
||||
self.appLocale = locale;
|
||||
this.updateAppLocaleState();
|
||||
},
|
||||
|
||||
/** 设置应用的语料数据 */
|
||||
setAppCorpusData(data: any = {}) {
|
||||
self.appCorpusData = data;
|
||||
this.updateAppLocaleState();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -636,6 +636,24 @@ export const EditorNode = types
|
||||
root.changeValueById(info.id, schema);
|
||||
},
|
||||
|
||||
updateSchemaStyle(value: any) {
|
||||
const info = self.info!;
|
||||
|
||||
if (info.editable === false) {
|
||||
return;
|
||||
}
|
||||
const root = getRoot(self) as any;
|
||||
let schema = root.getSchema(info.id);
|
||||
schema = {
|
||||
...schema,
|
||||
style: {
|
||||
...schema.style,
|
||||
...value
|
||||
}
|
||||
};
|
||||
root.changeValueById(info.id, schema);
|
||||
},
|
||||
|
||||
setComponent(value: any) {
|
||||
component = value;
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file 功能类函数集合。
|
||||
*/
|
||||
import {utils, hasIcon} from 'amis';
|
||||
import {utils, hasIcon, mapObject} from 'amis';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import {isObservable, reaction} from 'mobx';
|
||||
import DeepDiff, {Diff} from 'deep-diff';
|
||||
@ -993,7 +993,8 @@ export function unitFormula(insetStr: string, offsetVal: number) {
|
||||
insetUnit = 'px';
|
||||
}
|
||||
const newOffsetVal = insetNum + curOffsetVal;
|
||||
return `${newOffsetVal >= 0 ? newOffsetVal : '0'}${insetUnit}`;
|
||||
return `${newOffsetVal}${insetUnit}`;
|
||||
// return `${newOffsetVal >= 0 ? newOffsetVal : '0'}${insetUnit}`; // 限制拖拽区域
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1022,10 +1023,63 @@ export function needDefaultWidth(elemType: string) {
|
||||
'progress',
|
||||
'diff-editor',
|
||||
'editor',
|
||||
'input-range'
|
||||
'input-range',
|
||||
'flex'
|
||||
];
|
||||
if (needDefaultWidthElemType.indexOf(elemType) > -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 是否开启应用国际化 */
|
||||
export function getI18nEnabled() {
|
||||
return (window as any)?.editorStore?.i18nEnabled ?? false;
|
||||
}
|
||||
|
||||
/** schema 翻译方法 */
|
||||
export function translateSchema(schema: any, replaceData?: any) {
|
||||
replaceData = replaceData || (window as any)?.editorStore?.appCorpusData;
|
||||
if (!isPlainObject(replaceData)) {
|
||||
return schema;
|
||||
}
|
||||
return mapObject(schema, (item: any) => {
|
||||
return replaceData[item] || item;
|
||||
});
|
||||
}
|
||||
|
||||
/** 应用级别的翻译方法 */
|
||||
export function appTranslate(value?: string) {
|
||||
if (!isString(value)) {
|
||||
return value;
|
||||
}
|
||||
return (window as any)?.editorStore?.appCorpusData?.[value!] || value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要给组件增加填充占位样式
|
||||
*/
|
||||
export function needFillPlaceholder(curProps: any) {
|
||||
let needFillPlaceholder = false;
|
||||
if (!curProps) {
|
||||
return false;
|
||||
}
|
||||
// 识别page中的aside、body
|
||||
if (
|
||||
curProps.rendererName === 'page' &&
|
||||
(curProps.name === 'aside' || curProps.name === 'body')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// 识别自由容器
|
||||
if (curProps.node?.schema?.isFreeContainer) {
|
||||
return true;
|
||||
}
|
||||
// 支持在plugin中配置
|
||||
if (curProps.$$editor?.needFillPlaceholder) {
|
||||
needFillPlaceholder = true;
|
||||
} else if (curProps.regionConfig?.needFillPlaceholder) {
|
||||
needFillPlaceholder = true;
|
||||
}
|
||||
return needFillPlaceholder;
|
||||
}
|
||||
|
256
packages/amis-editor-core/src/variable.ts
Normal file
256
packages/amis-editor-core/src/variable.ts
Normal file
@ -0,0 +1,256 @@
|
||||
/**
|
||||
* @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, eachTree} 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');
|
||||
}
|
||||
eachTree(options, item => {
|
||||
if (item.type === 'array') {
|
||||
delete item.children;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面变量树形结构
|
||||
* @returns
|
||||
*/
|
||||
getPageVariablesOptions() {
|
||||
let options: Option[] = [];
|
||||
|
||||
const pageScope = this.dataSchema?.root.children?.filter(
|
||||
item => item.tag === '页面变量'
|
||||
)[0];
|
||||
if (pageScope) {
|
||||
options = pageScope.getDataPropsAsOptions();
|
||||
}
|
||||
eachTree(options, item => {
|
||||
if (item.type === 'array') {
|
||||
delete item.children;
|
||||
}
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据变量路径获取变量名称
|
||||
*/
|
||||
getNameByPath(path: string, valueField = 'value', labelField = 'label') {
|
||||
if (!path || typeof path !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const options = [
|
||||
...this.getVariableOptions(),
|
||||
...this.getPageVariablesOptions()
|
||||
];
|
||||
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