mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:48:45 +08:00
feat: 编辑器支持只读模式 (#10857)
* feat: 扩展编辑器功能 * 弹框支持readonly * bugfix * bugfix --------- Co-authored-by: qinhaoyan <30946345+qinhaoyan@users.noreply.github.com> Co-authored-by: hsm-lv <80095014+hsm-lv@users.noreply.github.com>
This commit is contained in:
parent
859ae8d72a
commit
13f1890db7
@ -83,6 +83,7 @@
|
||||
min-width: 980px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
|
||||
// 覆盖amis默认top值,避免导致未垂直居中
|
||||
.ae-Editor-toolbar svg.icon {
|
||||
@ -125,6 +126,18 @@
|
||||
border: 1px dashed rgb(206, 208, 211);
|
||||
}
|
||||
|
||||
.subEditor-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
& .subEditor-dialog {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗编辑器面板
|
||||
.subEditor-dialog {
|
||||
overflow: hidden;
|
||||
|
@ -135,6 +135,7 @@ export interface EditorProps extends PluginEventListener {
|
||||
getHostNodeDataSchema?: () => Promise<any>;
|
||||
|
||||
getAvaiableContextFields?: (node: EditorNodeType) => Promise<any>;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export default class Editor extends Component<EditorProps> {
|
||||
@ -275,6 +276,10 @@ export default class Editor extends Component<EditorProps> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const manager = this.manager;
|
||||
const store = manager.store;
|
||||
|
||||
@ -573,7 +578,8 @@ export default class Editor extends Component<EditorProps> {
|
||||
previewProps,
|
||||
autoFocus,
|
||||
isSubEditor,
|
||||
amisEnv
|
||||
amisEnv,
|
||||
readonly
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -588,7 +594,7 @@ export default class Editor extends Component<EditorProps> {
|
||||
)}
|
||||
>
|
||||
<div className="ae-Editor-inner" onContextMenu={this.handleContextMenu}>
|
||||
{!preview && (
|
||||
{!preview && !readonly && (
|
||||
<LeftPanels
|
||||
store={this.store}
|
||||
manager={this.manager}
|
||||
@ -618,6 +624,7 @@ export default class Editor extends Component<EditorProps> {
|
||||
amisEnv={amisEnv}
|
||||
autoFocus={autoFocus}
|
||||
toolbarContainer={this.getToolbarContainer}
|
||||
readonly={readonly}
|
||||
></Preview>
|
||||
</div>
|
||||
|
||||
@ -628,6 +635,7 @@ export default class Editor extends Component<EditorProps> {
|
||||
theme={theme}
|
||||
appLocale={appLocale}
|
||||
amisEnv={amisEnv}
|
||||
readonly={readonly}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -639,6 +647,7 @@ export default class Editor extends Component<EditorProps> {
|
||||
manager={this.manager}
|
||||
theme={theme}
|
||||
amisEnv={amisEnv}
|
||||
readonly={readonly}
|
||||
/>
|
||||
<ScaffoldModal
|
||||
store={this.store}
|
||||
|
@ -19,6 +19,7 @@ export interface HighlightBoxProps {
|
||||
onSwitch?: (id: string) => void;
|
||||
manager: EditorManager;
|
||||
children?: React.ReactNode;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export default observer(function ({
|
||||
@ -30,7 +31,8 @@ export default observer(function ({
|
||||
node,
|
||||
toolbarContainer,
|
||||
onSwitch,
|
||||
manager
|
||||
manager,
|
||||
readonly
|
||||
}: HighlightBoxProps) {
|
||||
const handleWResizerMouseDown = React.useCallback(
|
||||
(e: MouseEvent) => startResize(e, 'horizontal'),
|
||||
@ -250,7 +252,7 @@ export default observer(function ({
|
||||
draggable={!!curFreeContainerId || isDraggableContainer}
|
||||
onDragStart={handleDragStart}
|
||||
>
|
||||
{isActive ? (
|
||||
{isActive && !readonly ? (
|
||||
<div
|
||||
className={`ae-Editor-toolbarPopover ${
|
||||
isRightElem ? 'is-right-elem' : ''
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {observer} from 'mobx-react';
|
||||
import React from 'react';
|
||||
import {Tab, Tabs} from 'amis';
|
||||
import {Tab, Tabs, toast} from 'amis';
|
||||
import cx from 'classnames';
|
||||
import {EditorManager} from '../../manager';
|
||||
import {EditorStoreType} from '../../store/editor';
|
||||
@ -16,6 +16,7 @@ interface RightPanelsProps {
|
||||
theme?: string;
|
||||
appLocale?: string;
|
||||
amisEnv?: any;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
interface RightPanelsStates {
|
||||
@ -62,6 +63,29 @@ export class RightPanels extends React.Component<
|
||||
return findDOMNode(this) as HTMLElement;
|
||||
}
|
||||
|
||||
@autobind
|
||||
handlePanelChangeValue(
|
||||
...arg: Parameters<typeof this.props.manager.panelChangeValue>
|
||||
) {
|
||||
const {manager, readonly} = this.props;
|
||||
|
||||
if (readonly) {
|
||||
const diff = arg[1];
|
||||
if (
|
||||
!diff?.find((item: any) =>
|
||||
item.path.find(
|
||||
(p: string) => !p.startsWith('__') && p !== 'pullRefresh'
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
toast.error('不支持编辑');
|
||||
} else {
|
||||
manager.panelChangeValue(...arg);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {store, manager, theme} = this.props;
|
||||
const {isOpenStatus, isFixedStatus} = this.state;
|
||||
@ -77,7 +101,7 @@ export class RightPanels extends React.Component<
|
||||
path: node?.path,
|
||||
node: node,
|
||||
value: store.value,
|
||||
onChange: manager.panelChangeValue,
|
||||
onChange: this.handlePanelChangeValue,
|
||||
store: store,
|
||||
manager: manager,
|
||||
popOverContainer: this.getPopOverContainer
|
||||
@ -90,7 +114,7 @@ export class RightPanels extends React.Component<
|
||||
info={node?.info}
|
||||
path={node?.path}
|
||||
value={store.value}
|
||||
onChange={manager.panelChangeValue}
|
||||
onChange={this.handlePanelChangeValue}
|
||||
store={store}
|
||||
manager={manager}
|
||||
popOverContainer={this.getPopOverContainer}
|
||||
|
@ -39,6 +39,8 @@ export interface PreviewProps {
|
||||
autoFocus?: boolean;
|
||||
|
||||
toolbarContainer?: () => any;
|
||||
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
export interface PreviewState {
|
||||
@ -604,9 +606,11 @@ export default class Preview extends Component<PreviewProps> {
|
||||
toolbarContainer={toolbarContainer}
|
||||
onSwitch={this.handleNavSwitch}
|
||||
manager={manager}
|
||||
readonly={this.props.readonly}
|
||||
>
|
||||
{node.childRegions.map(region =>
|
||||
!node.memberImmutable(region.region) &&
|
||||
!this.props.readonly &&
|
||||
store.isRegionActive(region.id, region.region) ? (
|
||||
<RegionHighlightBox
|
||||
manager={manager}
|
||||
|
@ -19,6 +19,7 @@ export interface SubEditorProps {
|
||||
manager: EditorManager;
|
||||
theme?: string;
|
||||
amisEnv?: RenderOptions;
|
||||
readonly?: boolean;
|
||||
}
|
||||
|
||||
@observer
|
||||
@ -97,7 +98,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
|
||||
}
|
||||
|
||||
buildSchema() {
|
||||
const {store, manager, amisEnv} = this.props;
|
||||
const {store, manager, amisEnv, readonly} = this.props;
|
||||
const subEditorContext = store.subEditorContext;
|
||||
const config = manager.config;
|
||||
let superEditorData: any = store.superEditorData;
|
||||
@ -118,6 +119,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
|
||||
? {
|
||||
type: 'form',
|
||||
mode: 'normal',
|
||||
wrapWithPanel: false,
|
||||
wrapperComponent: 'div',
|
||||
onValidate: async (value: any) => {
|
||||
const result = await store.subEditorContext?.validate?.(value);
|
||||
@ -190,6 +192,7 @@ export class SubEditor extends React.Component<SubEditorProps> {
|
||||
getAvaiableContextFields={node =>
|
||||
manager.getAvailableContextFields(node)
|
||||
}
|
||||
readonly={readonly}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -244,10 +247,14 @@ export class SubEditor extends React.Component<SubEditorProps> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {store, theme, manager} = this.props;
|
||||
const {store, theme, manager, readonly} = this.props;
|
||||
if (!store.subEditorContext) {
|
||||
return null;
|
||||
}
|
||||
return render(
|
||||
{
|
||||
type: 'dialog',
|
||||
type: readonly ? 'container' : 'dialog',
|
||||
className: readonly ? 'subEditor-container' : 'subEditor-dialog',
|
||||
...this.buildSchema()
|
||||
},
|
||||
|
||||
|
@ -101,6 +101,8 @@ export function SchemaFrom({
|
||||
return schema;
|
||||
}, [body, controls, submitOnChange]);
|
||||
|
||||
const [init, setInit] = React.useState(true);
|
||||
|
||||
const themeConfig = React.useMemo(() => getThemeConfig(), []);
|
||||
const submitSubscribers = React.useRef<Array<Function>>([]);
|
||||
const subscribeSubmit = React.useCallback(
|
||||
@ -147,10 +149,10 @@ export function SchemaFrom({
|
||||
newValue = pipeOut ? await pipeOut(newValue, value) : newValue;
|
||||
const diffValue = diff(value, newValue);
|
||||
// 没有变化时不触发onChange
|
||||
if (!diffValue) {
|
||||
if (!diffValue || init) {
|
||||
setInit(false);
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(newValue, diffValue, (schema, value, id, diff) => {
|
||||
return submitSubscribers.current.reduce((schema, fn) => {
|
||||
return fn(schema, value, id, diff);
|
||||
|
@ -1029,6 +1029,9 @@ export abstract class BasePlugin implements PluginInterface {
|
||||
|
||||
static scene = ['global'];
|
||||
|
||||
name?: string;
|
||||
rendererName?: string;
|
||||
|
||||
/**
|
||||
* 如果配置里面有 rendererName 自动返回渲染器信息。
|
||||
* @param renderer
|
||||
@ -1279,6 +1282,13 @@ export abstract class BasePlugin implements PluginInterface {
|
||||
originalValue: node.schema.value // 记录原始值,循环引用检测需要
|
||||
} as any;
|
||||
}
|
||||
|
||||
getKeyAndName() {
|
||||
return {
|
||||
key: this.rendererName,
|
||||
name: this.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,8 @@ import {
|
||||
appTranslate,
|
||||
JSONGetByPath,
|
||||
addModal,
|
||||
mergeDefinitions
|
||||
mergeDefinitions,
|
||||
getModals
|
||||
} from '../../src/util';
|
||||
import {
|
||||
InsertEventContext,
|
||||
@ -404,6 +405,9 @@ export const MainStore = types
|
||||
): EditorNodeType | undefined {
|
||||
return self.root.getNodeById(id, regionOrType);
|
||||
},
|
||||
getNodeByComponentId(id: string): EditorNodeType | undefined {
|
||||
return self.root.getNodeByComponentId(id);
|
||||
},
|
||||
|
||||
get activeNodeInfo(): RendererInfo | null | undefined {
|
||||
return this.getNodeById(self.activeId)?.info;
|
||||
@ -1039,64 +1043,7 @@ export const MainStore = types
|
||||
// 获取弹窗大纲列表
|
||||
get modals(): Array<EditorModalBody> {
|
||||
const schema = self.schema;
|
||||
const modals: Array<DialogSchema | DrawerSchema> = [];
|
||||
|
||||
JSONTraverse(schema, (value: any, key: string, host: any) => {
|
||||
if (
|
||||
key === 'actionType' &&
|
||||
['dialog', 'drawer', 'confirmDialog'].includes(value)
|
||||
) {
|
||||
const key = value === 'drawer' ? 'drawer' : 'dialog';
|
||||
const body = host[key] || host['args'];
|
||||
if (
|
||||
body &&
|
||||
!body.$ref &&
|
||||
!modals.find(item => item.$$id === body.$$id)
|
||||
) {
|
||||
modals.push({
|
||||
...body,
|
||||
type: key,
|
||||
actionType: value
|
||||
});
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
// 公共组件排在前面
|
||||
Object.keys(schema.definitions || {})
|
||||
.reverse()
|
||||
.forEach(key => {
|
||||
const definition = schema.definitions[key];
|
||||
if (['dialog', 'drawer'].includes(definition.type)) {
|
||||
// 不要把已经内嵌弹窗中的弹窗再放到外面
|
||||
if (
|
||||
definition.$$originId &&
|
||||
modals.find(item => item.$$id === definition.$$originId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
modals.unshift({
|
||||
...definition,
|
||||
$$ref: key
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 子弹窗时,自己就是个弹窗
|
||||
if (['dialog', 'drawer', 'confirmDialog'].includes(schema.type)) {
|
||||
const idx = modals.findIndex(item => item.$$id === schema.$$id);
|
||||
if (~idx) {
|
||||
modals.splice(idx, 1);
|
||||
}
|
||||
|
||||
modals.unshift({
|
||||
...schema,
|
||||
// 如果还包含这个,子弹窗里面收集弹窗的时候会出现多份内嵌弹窗
|
||||
definitions: undefined
|
||||
});
|
||||
}
|
||||
const modals: Array<DialogSchema | DrawerSchema> = getModals(schema);
|
||||
|
||||
return modals;
|
||||
},
|
||||
@ -1367,7 +1314,8 @@ export const MainStore = types
|
||||
setActiveId(
|
||||
id: string,
|
||||
region: string = '',
|
||||
selections: Array<string> = []
|
||||
selections: Array<string> = [],
|
||||
onEditorActive: boolean = true
|
||||
) {
|
||||
const node = id ? self.getNodeById(id) : undefined;
|
||||
|
||||
@ -1381,6 +1329,39 @@ export const MainStore = types
|
||||
// if (!self.panelKey && id) {
|
||||
// self.panelKey = 'config';
|
||||
// }
|
||||
const schema = self.getSchema(id);
|
||||
|
||||
onEditorActive && (window as any).onEditorActive?.(schema);
|
||||
},
|
||||
|
||||
setActiveIdByComponentId(id: string) {
|
||||
const node = self.getNodeByComponentId(id);
|
||||
if (node) {
|
||||
this.setActiveId(node.id, node.region, [], false);
|
||||
this.closeSubEditor();
|
||||
} else {
|
||||
const modals = self.modals;
|
||||
const modalSchema = find(modals, modal => modal.id === id);
|
||||
if (modalSchema) {
|
||||
this.openSubEditor({
|
||||
value: modalSchema,
|
||||
title: '弹窗预览',
|
||||
onChange: (value: any) => {}
|
||||
});
|
||||
} else {
|
||||
const subEditorRef = this.getSubEditorRef();
|
||||
if (subEditorRef) {
|
||||
subEditorRef.store.setActiveIdByComponentId(id);
|
||||
const $$id = subEditorRef.props.value.$$id;
|
||||
const modalSchema = find(modals, modal => modal.$$id === $$id);
|
||||
this.openSubEditor({
|
||||
value: modalSchema,
|
||||
title: '弹窗预览',
|
||||
onChange: (value: any) => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setSelections(ids: Array<string>) {
|
||||
|
@ -104,6 +104,28 @@ export const EditorNode = types
|
||||
return resolved;
|
||||
},
|
||||
|
||||
getNodeByComponentId(id: string) {
|
||||
let pool = self.children.concat();
|
||||
let resolved: any = undefined;
|
||||
|
||||
while (pool.length) {
|
||||
const item = pool.shift();
|
||||
const schema = item.schema;
|
||||
|
||||
if (schema && schema.id === id) {
|
||||
resolved = item;
|
||||
break;
|
||||
}
|
||||
|
||||
// 将当前节点的子节点全部放置到 pool中
|
||||
if (item.children.length) {
|
||||
pool.push.apply(pool, item.uniqueChildren);
|
||||
}
|
||||
}
|
||||
|
||||
return resolved;
|
||||
},
|
||||
|
||||
setInfo(value: RendererInfo) {
|
||||
info = value;
|
||||
},
|
||||
|
@ -23,6 +23,8 @@ import merge from 'lodash/merge';
|
||||
import {EditorModalBody} from './store/editor';
|
||||
import {filter} from 'lodash';
|
||||
import type {SchemaType} from 'amis/lib/Schema';
|
||||
import type {DialogSchema} from '../../amis/src/renderers/Dialog';
|
||||
import type {DrawerSchema} from '../../amis/src/renderers/Drawer';
|
||||
|
||||
const {
|
||||
guid,
|
||||
@ -1852,6 +1854,62 @@ export function setDefaultColSize(
|
||||
return tempList;
|
||||
}
|
||||
|
||||
export function getModals(schema: any) {
|
||||
const modals: Array<DialogSchema | DrawerSchema> = [];
|
||||
JSONTraverse(schema, (value: any, key: string, host: any) => {
|
||||
if (
|
||||
key === 'actionType' &&
|
||||
['dialog', 'drawer', 'confirmDialog'].includes(value)
|
||||
) {
|
||||
const key = value === 'drawer' ? 'drawer' : 'dialog';
|
||||
const body = host[key] || host['args'];
|
||||
if (body && !body.$ref && !modals.find(item => item.$$id === body.$$id)) {
|
||||
modals.push({
|
||||
...body,
|
||||
type: key,
|
||||
actionType: value
|
||||
});
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
// 公共组件排在前面
|
||||
Object.keys(schema.definitions || {})
|
||||
.reverse()
|
||||
.forEach(key => {
|
||||
const definition = schema.definitions[key];
|
||||
if (['dialog', 'drawer'].includes(definition.type)) {
|
||||
// 不要把已经内嵌弹窗中的弹窗再放到外面
|
||||
if (
|
||||
definition.$$originId &&
|
||||
modals.find(item => item.$$id === definition.$$originId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
modals.unshift({
|
||||
...definition,
|
||||
$$ref: key
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 子弹窗时,自己就是个弹窗
|
||||
if (['dialog', 'drawer', 'confirmDialog'].includes(schema.type)) {
|
||||
const idx = modals.findIndex(item => item.$$id === schema.$$id);
|
||||
if (~idx) {
|
||||
modals.splice(idx, 1);
|
||||
}
|
||||
|
||||
modals.unshift({
|
||||
...schema,
|
||||
// 如果还包含这个,子弹窗里面收集弹窗的时候会出现多份内嵌弹窗
|
||||
definitions: undefined
|
||||
});
|
||||
}
|
||||
return modals;
|
||||
}
|
||||
export const RAW_TYPE_MAP: {
|
||||
[k in SchemaType | 'user-select' | 'department-select']?:
|
||||
| 'string'
|
||||
|
Loading…
Reference in New Issue
Block a user