mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-29 18:39:05 +08:00
feat: 表单支持flex布局 (#10286)
* feat: form-row-dnd * form-dnd * feat: form-flex-layout * feat: form-flex-layout * feat: 处理移动端独占一行的组件 * feat: 处理移动端独占一行的组件 * feat: 处理移动端独占一行的组件 * feat: 处理移动端独占一行的组件 * feat: 编辑器头部右侧提供容器 * fix: 修复移动端插入报错问题 * style: 优化form flex模式下移动端组件样式 * style: 优化flex dnd指示器样式 * chore: LabelAlign支持inherit值 * feat: editor addElem 支持 nocode-form * colSize * 初始时获取第一个region * form row 改为可选 * form 去掉 dndMode * feat: 状态插件支持选择自定义表达式、校验插件新增清空方法、增加两种校验类型 (#10239) * feat:基础插件优化、增加校验类型 * fix:去除默认options --------- Co-authored-by: hezhihang <hezhihang@baidu.com> * style: 优化flex dnd指示器样式 * fix: 修复标题位置继承错误问题 * fix: 修复colSize不实时渲染问题 * style: 优化零代码表单样式 * feat: xxxOn 支持自定义条件 * feat: xxxOn 支持自定义条件 * feat: xxxOn 支持自定义条件 * fix: 未验证的动态方法调用 * feat: 组件支持配置选中不高亮 * style: 修复input-number单个单位的样式 * fix: 导出校验相关函数 (#10263) Co-authored-by: hezhihang <hezhihang@baidu.com> * style: 表单组件不限制最大宽度 * feat: 支持配置面板内其他组件更新校验项时校验信息自动刷新 (#10279) * fix(editor): 修复删除节点后未正确赋值父级region问题 * fix(editor): 修复拖拽后组件高亮异常问题 * fix(editor): 修复表单组件样式 * fix(editor): 修改文案 * 修复内置校验可关闭的问题 (#10281) * fix: 内置校验一定是不可关闭的 * feat(editor): 编辑器支持配置mini toolbal模式,仅保留基础的菜单功能 * bugfix * feat(editor): 编辑器可配置是否支持弹框 * 删除低码编辑器的表单flex模式 * bugfix * fix: 优化状态中条件设置的判断逻辑 (#10294) * fix: 导出校验相关函数 * fix: 优化条件设置逻辑 --------- Co-authored-by: hezhihang <hezhihang@baidu.com> --------- Co-authored-by: qinhaoyan <30946345+qinhaoyan@users.noreply.github.com> Co-authored-by: yupeng12 <yupeng12@baidu.com> Co-authored-by: Allen <yupeng.fe@qq.com> Co-authored-by: hzh11012 <43038692+hzh11012@users.noreply.github.com> Co-authored-by: hezhihang <hezhihang@baidu.com> Co-authored-by: hsm-lv <80095014+hsm-lv@users.noreply.github.com> Co-authored-by: yangwei9012 <yangwei9012@163.com>
This commit is contained in:
parent
5c3d7bcffd
commit
b427168b67
@ -1,6 +1,6 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {
|
import {
|
||||||
resolveCondition,
|
resolveConditionAsync,
|
||||||
guid,
|
guid,
|
||||||
registerConditionComputer,
|
registerConditionComputer,
|
||||||
setConditionComputeErrorHandler
|
setConditionComputeErrorHandler
|
||||||
@ -283,13 +283,13 @@ const conditions7 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test(`condition`, async () => {
|
test(`condition`, async () => {
|
||||||
expect(await resolveCondition(conditions1, data)).toBe(false);
|
expect(await resolveConditionAsync(conditions1, data)).toBe(false);
|
||||||
expect(await resolveCondition(conditions2, data)).toBe(true);
|
expect(await resolveConditionAsync(conditions2, data)).toBe(true);
|
||||||
expect(await resolveCondition(conditions3, data)).toBe(false);
|
expect(await resolveConditionAsync(conditions3, data)).toBe(false);
|
||||||
expect(await resolveCondition(conditions4, data)).toBe(false);
|
expect(await resolveConditionAsync(conditions4, data)).toBe(false);
|
||||||
expect(await resolveCondition(conditions5, data)).toBe(false);
|
expect(await resolveConditionAsync(conditions5, data)).toBe(false);
|
||||||
expect(await resolveCondition(conditions6, data)).toBe(false);
|
expect(await resolveConditionAsync(conditions6, data)).toBe(false);
|
||||||
expect(await resolveCondition(conditions7, data)).toBe(false);
|
expect(await resolveConditionAsync(conditions7, data)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`condition date`, async () => {
|
test(`condition date`, async () => {
|
||||||
@ -381,7 +381,7 @@ test(`condition date`, async () => {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(await resolveCondition(conditions, data)).toBe(true);
|
expect(await resolveConditionAsync(conditions, data)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`condition tree`, async () => {
|
test(`condition tree`, async () => {
|
||||||
@ -466,9 +466,9 @@ test(`condition tree`, async () => {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(await resolveCondition(conditions8, data)).toBe(false);
|
expect(await resolveConditionAsync(conditions8, data)).toBe(false);
|
||||||
expect(await resolveCondition(conditions9, data)).toBe(true);
|
expect(await resolveConditionAsync(conditions9, data)).toBe(true);
|
||||||
expect(await resolveCondition(conditions10, data)).toBe(true);
|
expect(await resolveConditionAsync(conditions10, data)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`condition register`, async () => {
|
test(`condition register`, async () => {
|
||||||
@ -507,7 +507,7 @@ test(`condition register`, async () => {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(await resolveCondition(conditions, data)).toBe(true);
|
expect(await resolveConditionAsync(conditions, data)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`condition conditionComputeHander`, async () => {
|
test(`condition conditionComputeHander`, async () => {
|
||||||
@ -543,5 +543,5 @@ test(`condition conditionComputeHander`, async () => {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(await resolveCondition(conditions, data)).toBe(true);
|
expect(await resolveConditionAsync(conditions, data)).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import {RendererProps} from '../factory';
|
|||||||
import {ConditionGroupValue, Api, SchemaNode} from '../types';
|
import {ConditionGroupValue, Api, SchemaNode} from '../types';
|
||||||
import {createObject} from '../utils/helper';
|
import {createObject} from '../utils/helper';
|
||||||
import {RendererEvent} from '../utils/renderer-event';
|
import {RendererEvent} from '../utils/renderer-event';
|
||||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
import {evalExpressionWithConditionBuilderAsync} from '../utils/tpl';
|
||||||
import {dataMapping} from '../utils/tpl-builtin';
|
import {dataMapping} from '../utils/tpl-builtin';
|
||||||
import {IBreakAction} from './BreakAction';
|
import {IBreakAction} from './BreakAction';
|
||||||
import {IContinueAction} from './ContinueAction';
|
import {IContinueAction} from './ContinueAction';
|
||||||
@ -247,7 +247,7 @@ export const runAction = async (
|
|||||||
let isStop = false;
|
let isStop = false;
|
||||||
|
|
||||||
if (expression) {
|
if (expression) {
|
||||||
isStop = !(await evalExpressionWithConditionBuilder(
|
isStop = !(await evalExpressionWithConditionBuilderAsync(
|
||||||
expression,
|
expression,
|
||||||
mergeData,
|
mergeData,
|
||||||
true
|
true
|
||||||
@ -261,7 +261,7 @@ export const runAction = async (
|
|||||||
// 支持表达式 >=1.10.0
|
// 支持表达式 >=1.10.0
|
||||||
let preventDefault = false;
|
let preventDefault = false;
|
||||||
if (action.preventDefault) {
|
if (action.preventDefault) {
|
||||||
preventDefault = await evalExpressionWithConditionBuilder(
|
preventDefault = await evalExpressionWithConditionBuilderAsync(
|
||||||
action.preventDefault,
|
action.preventDefault,
|
||||||
mergeData,
|
mergeData,
|
||||||
false
|
false
|
||||||
@ -362,7 +362,7 @@ export const runAction = async (
|
|||||||
|
|
||||||
let stopPropagation = false;
|
let stopPropagation = false;
|
||||||
if (action.stopPropagation) {
|
if (action.stopPropagation) {
|
||||||
stopPropagation = await evalExpressionWithConditionBuilder(
|
stopPropagation = await evalExpressionWithConditionBuilderAsync(
|
||||||
action.stopPropagation,
|
action.stopPropagation,
|
||||||
mergeData,
|
mergeData,
|
||||||
false
|
false
|
||||||
|
@ -3,7 +3,7 @@ import {normalizeApi, normalizeApiResponseData} from '../utils/api';
|
|||||||
import {ServerError} from '../utils/errors';
|
import {ServerError} from '../utils/errors';
|
||||||
import {createObject, isEmpty} from '../utils/helper';
|
import {createObject, isEmpty} from '../utils/helper';
|
||||||
import {RendererEvent} from '../utils/renderer-event';
|
import {RendererEvent} from '../utils/renderer-event';
|
||||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
import {evalExpressionWithConditionBuilderAsync} from '../utils/tpl';
|
||||||
import {
|
import {
|
||||||
RendererAction,
|
RendererAction,
|
||||||
ListenerAction,
|
ListenerAction,
|
||||||
@ -61,7 +61,7 @@ export class AjaxAction implements RendererAction {
|
|||||||
|
|
||||||
if (api.sendOn !== undefined) {
|
if (api.sendOn !== undefined) {
|
||||||
// 发送请求前,判断是否需要发送
|
// 发送请求前,判断是否需要发送
|
||||||
const sendOn = await evalExpressionWithConditionBuilder(
|
const sendOn = await evalExpressionWithConditionBuilderAsync(
|
||||||
api.sendOn,
|
api.sendOn,
|
||||||
action.data ?? {},
|
action.data ?? {},
|
||||||
false
|
false
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {RendererEvent} from '../utils/renderer-event';
|
import {RendererEvent} from '../utils/renderer-event';
|
||||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
import {evalExpressionWithConditionBuilderAsync} from '../utils/tpl';
|
||||||
import {
|
import {
|
||||||
RendererAction,
|
RendererAction,
|
||||||
ListenerContext,
|
ListenerContext,
|
||||||
@ -27,7 +27,7 @@ export class SwitchAction implements RendererAction {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPass = await evalExpressionWithConditionBuilder(
|
const isPass = await evalExpressionWithConditionBuilderAsync(
|
||||||
branch.expression,
|
branch.expression,
|
||||||
mergeData
|
mergeData
|
||||||
);
|
);
|
||||||
|
@ -232,7 +232,7 @@ export interface FormSchemaBase {
|
|||||||
/**
|
/**
|
||||||
* 配置表单项默认的展示方式。
|
* 配置表单项默认的展示方式。
|
||||||
*/
|
*/
|
||||||
mode?: 'normal' | 'inline' | 'horizontal';
|
mode?: 'normal' | 'inline' | 'horizontal' | 'flex';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单项显示为几列
|
* 表单项显示为几列
|
||||||
@ -1735,6 +1735,7 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
otherProps: Partial<FormProps> = {}
|
otherProps: Partial<FormProps> = {}
|
||||||
): React.ReactNode {
|
): React.ReactNode {
|
||||||
children = children || [];
|
children = children || [];
|
||||||
|
const {classnames: cx} = this.props;
|
||||||
|
|
||||||
if (!Array.isArray(children)) {
|
if (!Array.isArray(children)) {
|
||||||
children = [children];
|
children = [children];
|
||||||
@ -1765,8 +1766,6 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {classnames: cx} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx('Form-row')}>
|
<div className={cx('Form-row')}>
|
||||||
{children.map((control, key) =>
|
{children.map((control, key) =>
|
||||||
@ -1789,6 +1788,63 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.mode === 'flex') {
|
||||||
|
let rows: any = [];
|
||||||
|
children.forEach(child => {
|
||||||
|
if (typeof child.row === 'number') {
|
||||||
|
if (rows[child.row]) {
|
||||||
|
rows[child.row].push(child);
|
||||||
|
} else {
|
||||||
|
rows[child.row] = [child];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有 row 的,就单启一行
|
||||||
|
rows.push([child]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{rows.map((children: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<div className={cx('Form-flex')} role="flex-row" key={index}>
|
||||||
|
{children.map((control: any, key: number) => {
|
||||||
|
const split = control.colSize?.split('/');
|
||||||
|
const colSize =
|
||||||
|
split?.[0] && split?.[1]
|
||||||
|
? (split[0] / split[1]) * 100 + '%'
|
||||||
|
: control.colSize;
|
||||||
|
return ~['hidden', 'formula'].indexOf(
|
||||||
|
(control as any).type
|
||||||
|
) ? (
|
||||||
|
this.renderChild(control, key, otherProps)
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
key={control.id || key}
|
||||||
|
className={cx(
|
||||||
|
`Form-flex-col`,
|
||||||
|
(control as Schema).columnClassName
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
flex:
|
||||||
|
colSize && !['1', 'auto'].includes(colSize)
|
||||||
|
? `0 0 ${colSize}`
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
role="flex-col"
|
||||||
|
>
|
||||||
|
{this.renderChild(control, '', {
|
||||||
|
...otherProps,
|
||||||
|
mode: 'flex'
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
return children.map((control, key) =>
|
return children.map((control, key) =>
|
||||||
this.renderChild(control, key, otherProps, region)
|
this.renderChild(control, key, otherProps, region)
|
||||||
);
|
);
|
||||||
@ -1841,7 +1897,10 @@ export default class Form extends React.Component<FormProps, object> {
|
|||||||
formSubmited: form.submited,
|
formSubmited: form.submited,
|
||||||
formMode: mode,
|
formMode: mode,
|
||||||
formHorizontal: horizontal,
|
formHorizontal: horizontal,
|
||||||
formLabelAlign: labelAlign !== 'left' ? 'right' : labelAlign,
|
formLabelAlign:
|
||||||
|
!labelAlign || !['left', 'right', 'top'].includes(labelAlign)
|
||||||
|
? 'right'
|
||||||
|
: labelAlign,
|
||||||
formLabelWidth: labelWidth,
|
formLabelWidth: labelWidth,
|
||||||
controlWidth,
|
controlWidth,
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +48,7 @@ import CustomStyle from '../components/CustomStyle';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import isPlainObject from 'lodash/isPlainObject';
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
|
|
||||||
export type LabelAlign = 'right' | 'left';
|
export type LabelAlign = 'right' | 'left' | 'top' | 'inherit';
|
||||||
|
|
||||||
export interface FormBaseControl extends BaseSchemaWithoutType {
|
export interface FormBaseControl extends BaseSchemaWithoutType {
|
||||||
/**
|
/**
|
||||||
@ -461,6 +461,8 @@ export interface FormBaseControl extends BaseSchemaWithoutType {
|
|||||||
* 初始化时是否把其他字段同步到表单内部。
|
* 初始化时是否把其他字段同步到表单内部。
|
||||||
*/
|
*/
|
||||||
initAutoFill?: boolean | 'fillIfNotSet';
|
initAutoFill?: boolean | 'fillIfNotSet';
|
||||||
|
|
||||||
|
row?: number; // flex模式下指定所在的行数
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
||||||
@ -1699,9 +1701,8 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
|||||||
themeCss,
|
themeCss,
|
||||||
id
|
id
|
||||||
} = props;
|
} = props;
|
||||||
const labelWidth = props.labelWidth || props.formLabelWidth;
|
|
||||||
description = description || desc;
|
description = description || desc;
|
||||||
|
const labelWidth = props.labelWidth || props.formLabelWidth;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-role="form-item"
|
data-role="form-item"
|
||||||
@ -1785,6 +1786,154 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
|||||||
</ul>
|
</ul>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{description && renderDescription !== false
|
||||||
|
? render('description', description, {
|
||||||
|
className: cx(
|
||||||
|
`Form-description`,
|
||||||
|
descriptionClassName,
|
||||||
|
setThemeClassName({
|
||||||
|
...props,
|
||||||
|
name: 'descriptionClassName',
|
||||||
|
id,
|
||||||
|
themeCss,
|
||||||
|
extra: 'item'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
flex: (props: FormItemProps, renderControl: () => JSX.Element) => {
|
||||||
|
let {
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
classnames: cx,
|
||||||
|
desc,
|
||||||
|
description,
|
||||||
|
label,
|
||||||
|
render,
|
||||||
|
required,
|
||||||
|
caption,
|
||||||
|
remark,
|
||||||
|
labelRemark,
|
||||||
|
env,
|
||||||
|
descriptionClassName,
|
||||||
|
captionClassName,
|
||||||
|
formItem: model,
|
||||||
|
renderLabel,
|
||||||
|
renderDescription,
|
||||||
|
hint,
|
||||||
|
data,
|
||||||
|
showErrorMsg,
|
||||||
|
mobileUI,
|
||||||
|
translate: __,
|
||||||
|
static: isStatic,
|
||||||
|
staticClassName,
|
||||||
|
wrapperCustomStyle,
|
||||||
|
themeCss,
|
||||||
|
id
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
let labelAlign =
|
||||||
|
(props.labelAlign !== 'inherit' && props.labelAlign) ||
|
||||||
|
props.formLabelAlign;
|
||||||
|
const labelWidth = props.labelWidth || props.formLabelWidth;
|
||||||
|
description = description || desc;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-role="form-item"
|
||||||
|
className={cx(
|
||||||
|
`Form-item Form-item--flex`,
|
||||||
|
isStatic && staticClassName ? staticClassName : className,
|
||||||
|
{
|
||||||
|
'is-error': model && !model.valid,
|
||||||
|
[`is-required`]: required
|
||||||
|
},
|
||||||
|
model?.errClassNames,
|
||||||
|
setThemeClassName({
|
||||||
|
...props,
|
||||||
|
name: 'wrapperCustomStyle',
|
||||||
|
id,
|
||||||
|
themeCss: wrapperCustomStyle,
|
||||||
|
extra: 'item'
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'Form-flexInner',
|
||||||
|
labelAlign && `Form-flexInner--label-${labelAlign}`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label && renderLabel !== false ? (
|
||||||
|
<label
|
||||||
|
className={cx(`Form-label`, getItemLabelClassName(props))}
|
||||||
|
style={
|
||||||
|
labelWidth != null
|
||||||
|
? {width: labelAlign === 'top' ? '100%' : labelWidth}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{render('label', label)}
|
||||||
|
{required && (label || labelRemark) ? (
|
||||||
|
<span className={cx(`Form-star`)}>*</span>
|
||||||
|
) : null}
|
||||||
|
{labelRemark
|
||||||
|
? render('label-remark', {
|
||||||
|
type: 'remark',
|
||||||
|
icon: labelRemark.icon || 'warning-mark',
|
||||||
|
tooltip: labelRemark,
|
||||||
|
className: cx(`Form-lableRemark`),
|
||||||
|
mobileUI,
|
||||||
|
container:
|
||||||
|
props.popOverContainer || env.getModalContainer
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{renderControl()}
|
||||||
|
|
||||||
|
{caption
|
||||||
|
? render('caption', caption, {
|
||||||
|
className: cx(`Form-caption`, captionClassName)
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{remark
|
||||||
|
? render('remark', {
|
||||||
|
type: 'remark',
|
||||||
|
icon: remark.icon || 'warning-mark',
|
||||||
|
className: cx(`Form-remark`),
|
||||||
|
tooltip: remark,
|
||||||
|
container: props.popOverContainer || env.getModalContainer
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hint && model && model.isFocused
|
||||||
|
? render('hint', hint, {
|
||||||
|
className: cx(`Form-hint`)
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{model &&
|
||||||
|
!model.valid &&
|
||||||
|
showErrorMsg !== false &&
|
||||||
|
Array.isArray(model.errors) ? (
|
||||||
|
<ul className={cx('Form-feedback')}>
|
||||||
|
{model.errors.map((msg: string, key: number) => (
|
||||||
|
<li key={key}>{msg}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{description && renderDescription !== false
|
{description && renderDescription !== false
|
||||||
? render('description', description, {
|
? render('description', description, {
|
||||||
className: cx(
|
className: cx(
|
||||||
@ -1940,7 +2089,12 @@ export const detectProps = [
|
|||||||
'displayMode',
|
'displayMode',
|
||||||
'revealPassword',
|
'revealPassword',
|
||||||
'loading',
|
'loading',
|
||||||
'themeCss'
|
'themeCss',
|
||||||
|
'formLabelAlign',
|
||||||
|
'formLabelWidth',
|
||||||
|
'formHorizontal',
|
||||||
|
'labelAlign',
|
||||||
|
'colSize'
|
||||||
];
|
];
|
||||||
|
|
||||||
export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||||
@ -2060,7 +2214,10 @@ export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
|||||||
...rest
|
...rest
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const controlSize = size || defaultSize;
|
const controlSize =
|
||||||
|
size && ['xs', 'sm', 'md', 'lg', 'full'].includes(size)
|
||||||
|
? size
|
||||||
|
: defaultSize;
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const isOpened = this.state.isOpened;
|
const isOpened = this.state.isOpened;
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import {evalExpression, filter} from './tpl';
|
import {
|
||||||
|
evalExpression,
|
||||||
|
evalExpressionWithConditionBuilder,
|
||||||
|
filter
|
||||||
|
} from './tpl';
|
||||||
import {PlainObject} from '../types';
|
import {PlainObject} from '../types';
|
||||||
import {injectPropsToObject, mapObject} from './helper';
|
import {injectPropsToObject, mapObject} from './helper';
|
||||||
import isPlainObject from 'lodash/isPlainObject';
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
@ -62,7 +66,8 @@ export function getExprProperties(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
value &&
|
value &&
|
||||||
typeof value === 'string' &&
|
(typeof value === 'string' ||
|
||||||
|
Object.prototype.toString.call(value) === '[object Object]') &&
|
||||||
parts?.[1] &&
|
parts?.[1] &&
|
||||||
(type === 'On' || type === 'Expr')
|
(type === 'On' || type === 'Expr')
|
||||||
) {
|
) {
|
||||||
@ -81,7 +86,9 @@ export function getExprProperties(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'On') {
|
if (type === 'On') {
|
||||||
value = props?.[key] || evalExpression(value, ctx || data);
|
value =
|
||||||
|
props?.[key] ||
|
||||||
|
evalExpressionWithConditionBuilder(value, ctx || data);
|
||||||
} else {
|
} else {
|
||||||
value = filter(value, ctx || data);
|
value = filter(value, ctx || data);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,11 @@ import {Options} from '../types';
|
|||||||
import isPlainObject from 'lodash/isPlainObject';
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
|
|
||||||
export function normalizeOptions(
|
export function normalizeOptions(
|
||||||
options: string | {[propName: string]: string} | Array<string> | Options,
|
options:
|
||||||
|
| string
|
||||||
|
| {[propName: string]: string}
|
||||||
|
| Array<string | number>
|
||||||
|
| Options,
|
||||||
share: {
|
share: {
|
||||||
values: Array<any>;
|
values: Array<any>;
|
||||||
options: Array<any>;
|
options: Array<any>;
|
||||||
@ -32,8 +36,9 @@ export function normalizeOptions(
|
|||||||
return option;
|
return option;
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (
|
||||||
Array.isArray(options as Array<string>) &&
|
Array.isArray(options as Array<string | number>) &&
|
||||||
typeof (options as Array<string>)[0] === 'string'
|
(typeof (options as Array<string | number>)[0] === 'string' ||
|
||||||
|
typeof (options as Array<string | number>)[0] === 'number')
|
||||||
) {
|
) {
|
||||||
return (options as Array<string>).map(item => {
|
return (options as Array<string>).map(item => {
|
||||||
const idx = share.values.indexOf(item);
|
const idx = share.values.indexOf(item);
|
||||||
|
@ -6,7 +6,7 @@ import {TreeItem, eachTree, getTree} from './helper';
|
|||||||
import {createObject, extendObject} from './object';
|
import {createObject, extendObject} from './object';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import {resolveVariableAndFilterForAsync} from './resolveVariableAndFilterForAsync';
|
import {resolveVariableAndFilterForAsync} from './resolveVariableAndFilterForAsync';
|
||||||
import {evalExpression, evalExpressionWithConditionBuilder} from './tpl';
|
import {evalExpression, evalExpressionWithConditionBuilderAsync} from './tpl';
|
||||||
|
|
||||||
export interface debounceConfig {
|
export interface debounceConfig {
|
||||||
maxWait?: number;
|
maxWait?: number;
|
||||||
@ -379,7 +379,7 @@ export async function getMatchedEventTargets<T extends TreeItem>(
|
|||||||
eachTree(tree, item => {
|
eachTree(tree, item => {
|
||||||
const data = item.storeType ? item.data : item;
|
const data = item.storeType ? item.data : item;
|
||||||
promies.push(async () => {
|
promies.push(async () => {
|
||||||
const result = await evalExpressionWithConditionBuilder(
|
const result = await evalExpressionWithConditionBuilderAsync(
|
||||||
condition,
|
condition,
|
||||||
createObject(ctx, data)
|
createObject(ctx, data)
|
||||||
);
|
);
|
||||||
|
@ -6,6 +6,7 @@ import startsWith from 'lodash/startsWith';
|
|||||||
import {resolveVariableAndFilterForAsync} from './resolveVariableAndFilterForAsync';
|
import {resolveVariableAndFilterForAsync} from './resolveVariableAndFilterForAsync';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import capitalize from 'lodash/capitalize';
|
import capitalize from 'lodash/capitalize';
|
||||||
|
import {isPureVariable, resolveVariableAndFilter} from './tpl-builtin';
|
||||||
|
|
||||||
const conditionResolverMap: {
|
const conditionResolverMap: {
|
||||||
[op: string]: (left: any, right: any, fieldType?: string) => boolean;
|
[op: string]: (left: any, right: any, fieldType?: string) => boolean;
|
||||||
@ -18,7 +19,7 @@ let conditionComputeErrorHandler: (
|
|||||||
defaultResult: boolean
|
defaultResult: boolean
|
||||||
) => boolean | Promise<boolean>;
|
) => boolean | Promise<boolean>;
|
||||||
|
|
||||||
export async function resolveCondition(
|
export async function resolveConditionAsync(
|
||||||
conditions: any,
|
conditions: any,
|
||||||
data: any,
|
data: any,
|
||||||
defaultResult: boolean = true
|
defaultResult: boolean = true
|
||||||
@ -33,7 +34,7 @@ export async function resolveCondition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await computeConditions(
|
return await computeConditionsAsync(
|
||||||
conditions.children,
|
conditions.children,
|
||||||
conditions.conjunction,
|
conditions.conjunction,
|
||||||
data
|
data
|
||||||
@ -51,7 +52,36 @@ export async function resolveCondition(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function computeConditions(
|
export function resolveCondition(
|
||||||
|
conditions: any,
|
||||||
|
data: any,
|
||||||
|
defaultResult: boolean = true
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!conditions ||
|
||||||
|
!conditions.conjunction ||
|
||||||
|
!Array.isArray(conditions.children) ||
|
||||||
|
!conditions.children.length
|
||||||
|
) {
|
||||||
|
return defaultResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return computeConditions(conditions.children, conditions.conjunction, data);
|
||||||
|
} catch (e) {
|
||||||
|
// 如果函数未定义,则交给handler
|
||||||
|
if (e.name === 'FormulaEvalError') {
|
||||||
|
return conditionComputeErrorHandler?.(
|
||||||
|
conditions.children,
|
||||||
|
conditions.conjunction,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return defaultResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function computeConditionsAsync(
|
||||||
conditions: any[],
|
conditions: any[],
|
||||||
conjunction: 'or' | 'and' = 'and',
|
conjunction: 'or' | 'and' = 'and',
|
||||||
data: any
|
data: any
|
||||||
@ -61,8 +91,8 @@ async function computeConditions(
|
|||||||
const item = conditions[index];
|
const item = conditions[index];
|
||||||
const result =
|
const result =
|
||||||
item.conjunction && Array.isArray(item.children) && item.children.length
|
item.conjunction && Array.isArray(item.children) && item.children.length
|
||||||
? await computeConditions(item.children, item.conjunction, data)
|
? await computeConditionsAsync(item.children, item.conjunction, data)
|
||||||
: await computeCondition(item, index, data);
|
: await computeConditionAsync(item, index, data);
|
||||||
|
|
||||||
computeResult = !!result;
|
computeResult = !!result;
|
||||||
|
|
||||||
@ -76,7 +106,32 @@ async function computeConditions(
|
|||||||
return computeResult;
|
return computeResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function computeCondition(
|
function computeConditions(
|
||||||
|
conditions: any[],
|
||||||
|
conjunction: 'or' | 'and' = 'and',
|
||||||
|
data: any
|
||||||
|
): boolean {
|
||||||
|
let computeResult = true;
|
||||||
|
for (let index = 0, len = conditions.length; index < len; index++) {
|
||||||
|
const item = conditions[index];
|
||||||
|
const result =
|
||||||
|
item.conjunction && Array.isArray(item.children) && item.children.length
|
||||||
|
? computeConditions(item.children, item.conjunction, data)
|
||||||
|
: computeCondition(item, index, data);
|
||||||
|
|
||||||
|
computeResult = !!result;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(result && conjunction === 'or') ||
|
||||||
|
(!result && conjunction === 'and')
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return computeResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function computeConditionAsync(
|
||||||
rule: {
|
rule: {
|
||||||
op: string;
|
op: string;
|
||||||
left: {
|
left: {
|
||||||
@ -104,6 +159,32 @@ async function computeCondition(
|
|||||||
return func ? func(leftValue, rightValue, rule.left.type) : DEFAULT_RESULT;
|
return func ? func(leftValue, rightValue, rule.left.type) : DEFAULT_RESULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeCondition(
|
||||||
|
rule: {
|
||||||
|
op: string;
|
||||||
|
left: {
|
||||||
|
type: string;
|
||||||
|
field: string;
|
||||||
|
};
|
||||||
|
right: any;
|
||||||
|
},
|
||||||
|
index: number,
|
||||||
|
data: any
|
||||||
|
) {
|
||||||
|
const leftValue = get(data, rule.left.field);
|
||||||
|
const rightValue: any = isPureVariable(rule.right)
|
||||||
|
? resolveVariableAndFilter(rule.right, data)
|
||||||
|
: rule.right;
|
||||||
|
|
||||||
|
const func =
|
||||||
|
conditionResolverMap[`${rule.op}For${capitalize(rule.left.type)}`] ??
|
||||||
|
conditionResolverMap[rule.op];
|
||||||
|
|
||||||
|
return func && typeof func === 'function'
|
||||||
|
? func(leftValue, rightValue, rule.left.type)
|
||||||
|
: DEFAULT_RESULT;
|
||||||
|
}
|
||||||
|
|
||||||
function startsWithFunc(left: any, right: any) {
|
function startsWithFunc(left: any, right: any) {
|
||||||
if (left === undefined || right === undefined) {
|
if (left === undefined || right === undefined) {
|
||||||
return DEFAULT_RESULT;
|
return DEFAULT_RESULT;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {register as registerBulitin, getFilters} from './tpl-builtin';
|
import {register as registerBulitin, getFilters} from './tpl-builtin';
|
||||||
import {register as registerLodash} from './tpl-lodash';
|
import {register as registerLodash} from './tpl-lodash';
|
||||||
import {parse, evaluate} from 'amis-formula';
|
import {parse, evaluate} from 'amis-formula';
|
||||||
import {resolveCondition} from './resolveCondition';
|
import {resolveCondition, resolveConditionAsync} from './resolveCondition';
|
||||||
import {memoParse} from './tokenize';
|
import {memoParse} from './tokenize';
|
||||||
|
|
||||||
export interface Enginer {
|
export interface Enginer {
|
||||||
@ -133,14 +133,33 @@ export function evalExpression(expression: string, data?: object): boolean {
|
|||||||
* @param data 上下文
|
* @param data 上下文
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function evalExpressionWithConditionBuilder(
|
export async function evalExpressionWithConditionBuilderAsync(
|
||||||
expression: any,
|
expression: any,
|
||||||
data?: object,
|
data?: object,
|
||||||
defaultResult?: boolean
|
defaultResult?: boolean
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// 支持ConditionBuilder
|
// 支持ConditionBuilder
|
||||||
if (Object.prototype.toString.call(expression) === '[object Object]') {
|
if (Object.prototype.toString.call(expression) === '[object Object]') {
|
||||||
return await resolveCondition(expression, data, defaultResult);
|
return await resolveConditionAsync(expression, data, defaultResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return evalExpression(String(expression), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析表达式(支持condition-builder)
|
||||||
|
* @param expression 表达式 or condition-builder对象
|
||||||
|
* @param data 上下文
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function evalExpressionWithConditionBuilder(
|
||||||
|
expression: any,
|
||||||
|
data?: object,
|
||||||
|
defaultResult?: boolean
|
||||||
|
) {
|
||||||
|
// 支持ConditionBuilder
|
||||||
|
if (Object.prototype.toString.call(expression) === '[object Object]') {
|
||||||
|
return resolveCondition(expression, data, defaultResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
return evalExpression(String(expression), data);
|
return evalExpression(String(expression), data);
|
||||||
|
@ -4,12 +4,13 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-bottom: 1px solid $editor-border-color;
|
border-bottom: 1px solid $editor-border-color;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
.ae-Breadcrumb-content {
|
.ae-Breadcrumb-content {
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
padding: 0 16px;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
@ -80,6 +80,7 @@
|
|||||||
min-height: 450px;
|
min-height: 450px;
|
||||||
min-width: 980px;
|
min-width: 980px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
// 覆盖amis默认top值,避免导致未垂直居中
|
// 覆盖amis默认top值,避免导致未垂直居中
|
||||||
.ae-Editor-toolbar svg.icon {
|
.ae-Editor-toolbar svg.icon {
|
||||||
@ -1199,7 +1200,7 @@
|
|||||||
|
|
||||||
[data-editor-id].ae-is-draging,
|
[data-editor-id].ae-is-draging,
|
||||||
.ae-is-draging {
|
.ae-is-draging {
|
||||||
display: none !important;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-editor-id][data-visible='false'] {
|
[data-editor-id][data-visible='false'] {
|
||||||
@ -1532,6 +1533,34 @@ div.ae-DragImage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ae-PushHighlight-top,
|
||||||
|
.ae-PushHighlight-bottom {
|
||||||
|
position: absolute;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: block;
|
||||||
|
background: $Editor-theme-color;
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ae-PushHighlight-left,
|
||||||
|
.ae-PushHighlight-right {
|
||||||
|
position: absolute;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: block;
|
||||||
|
background: $Editor-theme-color;
|
||||||
|
position: absolute;
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ae-DragGhost {
|
.ae-DragGhost {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
|
||||||
@ -1872,3 +1901,19 @@ div.ae-DragImage {
|
|||||||
div[class*='Form-group']:empty {
|
div[class*='Form-group']:empty {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
.ae-Header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: #fff;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid $editor-border-color;
|
||||||
|
.ae-Breadcrumb {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
&-Right-Container {
|
||||||
|
z-index: 1001;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -152,7 +152,8 @@ export default class Breadcrumb extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const scrollLeft = this.toNumber(this.getScrollLeft());
|
const scrollLeft = this.toNumber(this.getScrollLeft());
|
||||||
const maxScrollLeft = scrollElem.offsetWidth - scrollContainer.offsetWidth;
|
const maxScrollLeft =
|
||||||
|
scrollElem.offsetWidth - scrollContainer.offsetWidth + 32;
|
||||||
|
|
||||||
if (scrollLeft - 50 > -maxScrollLeft) {
|
if (scrollLeft - 50 > -maxScrollLeft) {
|
||||||
scrollElem.style.left = `${scrollLeft - 50}px`;
|
scrollElem.style.left = `${scrollLeft - 50}px`;
|
||||||
|
@ -31,6 +31,10 @@ export interface EditorProps extends PluginEventListener {
|
|||||||
$schemaUrl?: string;
|
$schemaUrl?: string;
|
||||||
schemas?: Array<any>;
|
schemas?: Array<any>;
|
||||||
theme?: string;
|
theme?: string;
|
||||||
|
/** 工具栏模式 */
|
||||||
|
toolbarMode?: 'default' | 'mini';
|
||||||
|
/** 是否需要弹框 */
|
||||||
|
noDialog?: boolean;
|
||||||
/** 应用语言类型 */
|
/** 应用语言类型 */
|
||||||
appLocale?: string;
|
appLocale?: string;
|
||||||
/** 是否开启多语言 */
|
/** 是否开启多语言 */
|
||||||
@ -166,6 +170,8 @@ export default class Editor extends Component<EditorProps> {
|
|||||||
{
|
{
|
||||||
isMobile: props.isMobile,
|
isMobile: props.isMobile,
|
||||||
theme: props.theme,
|
theme: props.theme,
|
||||||
|
toolbarMode: props.toolbarMode || 'default',
|
||||||
|
noDialog: props.noDialog,
|
||||||
isSubEditor,
|
isSubEditor,
|
||||||
amisDocHost: props.amisDocHost,
|
amisDocHost: props.amisDocHost,
|
||||||
superEditorData,
|
superEditorData,
|
||||||
@ -592,7 +598,13 @@ export default class Editor extends Component<EditorProps> {
|
|||||||
|
|
||||||
<div className="ae-Main">
|
<div className="ae-Main">
|
||||||
{!preview && (
|
{!preview && (
|
||||||
|
<div className="ae-Header">
|
||||||
<Breadcrumb store={this.store} manager={this.manager} />
|
<Breadcrumb store={this.store} manager={this.manager} />
|
||||||
|
<div
|
||||||
|
id="aeHeaderRightContainer"
|
||||||
|
className="ae-Header-Right-Container"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<Preview
|
<Preview
|
||||||
{...previewProps}
|
{...previewProps}
|
||||||
|
@ -238,7 +238,8 @@ export default observer(function ({
|
|||||||
)}
|
)}
|
||||||
data-hlbox-id={id}
|
data-hlbox-id={id}
|
||||||
style={{
|
style={{
|
||||||
display: node.w && node.h ? 'block' : 'none',
|
display:
|
||||||
|
node.w && node.h && !node.info.plugin.notHighlight ? 'block' : 'none',
|
||||||
top: node.y,
|
top: node.y,
|
||||||
left: node.x,
|
left: node.x,
|
||||||
width: node.w,
|
width: node.w,
|
||||||
|
@ -334,7 +334,7 @@ export class OutlinePanel extends React.Component<PanelProps> {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
{store.isSubEditor ? null : (
|
{store.isSubEditor || store.noDialog ? null : (
|
||||||
<Tab
|
<Tab
|
||||||
className={'ae-outline-tabs-panel'}
|
className={'ae-outline-tabs-panel'}
|
||||||
key={'dialog-outline'}
|
key={'dialog-outline'}
|
||||||
|
@ -635,8 +635,12 @@ class SmartPreview extends React.Component<SmartPreviewProps> {
|
|||||||
store.outline,
|
store.outline,
|
||||||
item => !item.isRegion && item.clickable
|
item => !item.isRegion && item.clickable
|
||||||
);
|
);
|
||||||
|
if (first && isAlive(store)) {
|
||||||
first && isAlive(store) && store.setActiveId(first.id);
|
const region = first.childRegions.find(
|
||||||
|
(i: any) => i.region
|
||||||
|
)?.region;
|
||||||
|
store.setActiveId(first.id, region);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 350);
|
}, 350);
|
||||||
} else {
|
} else {
|
||||||
|
285
packages/amis-editor-core/src/dnd/flex.ts
Normal file
285
packages/amis-editor-core/src/dnd/flex.ts
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
import {isMobile} from 'amis-core';
|
||||||
|
/**
|
||||||
|
* 支持上下左右拖拽模式
|
||||||
|
*/
|
||||||
|
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';
|
||||||
|
import findLastIndex from 'lodash/findLastIndex';
|
||||||
|
import find from 'lodash/find';
|
||||||
|
|
||||||
|
const className = 'PushHighlight';
|
||||||
|
|
||||||
|
export class FlexDNDMode implements DNDModeInterface {
|
||||||
|
readonly dndContainer: HTMLElement; // 记录当前拖拽区域
|
||||||
|
dropBeforeId?: string;
|
||||||
|
position?: 'top' | 'bottom' | 'left' | 'right';
|
||||||
|
maxRolLength = 4;
|
||||||
|
dragNode?: any;
|
||||||
|
dragId: string;
|
||||||
|
store: any;
|
||||||
|
constructor(
|
||||||
|
readonly dnd: EditorDNDManager,
|
||||||
|
readonly region: EditorNodeType,
|
||||||
|
config: any
|
||||||
|
) {
|
||||||
|
// 初始化时,默认将元素所在区域设置为当前拖拽区域
|
||||||
|
this.dndContainer = this.dnd.store
|
||||||
|
.getDoc()
|
||||||
|
.querySelector(
|
||||||
|
`[data-region="${region.region}"][data-region-host="${region.id}"]`
|
||||||
|
) as HTMLElement;
|
||||||
|
this.maxRolLength = config.regionNode.maxRolLength || 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首次拖入,把 ghost 插入进来。让用户有个直观感受。
|
||||||
|
* @param e
|
||||||
|
* @param ghost
|
||||||
|
*/
|
||||||
|
enter(e: DragEvent, ghost: HTMLElement) {
|
||||||
|
const dragEl = this.dnd.dragElement;
|
||||||
|
const list = Array.isArray(this.region.schema) ? this.region.schema : [];
|
||||||
|
const manager = this.dnd.manager;
|
||||||
|
this.store = manager.store;
|
||||||
|
// 如果区域里面没有元素,ghost就渲染为真实的表单元素
|
||||||
|
if (list.length === 0) {
|
||||||
|
if (dragEl && dragEl.closest('[data-region]') === this.dndContainer) {
|
||||||
|
const child = this.getChild(this.dndContainer, dragEl);
|
||||||
|
this.dndContainer.insertBefore(ghost, child);
|
||||||
|
let innerHTML = dragEl.outerHTML
|
||||||
|
.replace('ae-is-draging', '')
|
||||||
|
.replace(/\bdata\-editor\-id=(?:'.+?'|".+?")/g, '');
|
||||||
|
ghost.innerHTML = innerHTML;
|
||||||
|
} else {
|
||||||
|
renderThumbToGhost(
|
||||||
|
ghost,
|
||||||
|
this.region,
|
||||||
|
translateSchema(this.store.dragSchema),
|
||||||
|
manager
|
||||||
|
);
|
||||||
|
this.dndContainer.appendChild(ghost);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ghost.innerHTML = '';
|
||||||
|
// 直接插入 ghost,over的时候再去调整样式
|
||||||
|
this.dndContainer.appendChild(ghost);
|
||||||
|
}
|
||||||
|
this.dragId = this.store.dragId;
|
||||||
|
this.dragNode =
|
||||||
|
find(list, (item: any) => item.$$id === this.dragId) ||
|
||||||
|
this.store.dragSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拖出去了,就移除 ghost
|
||||||
|
* @param e
|
||||||
|
* @param ghost
|
||||||
|
*/
|
||||||
|
leave(e: DragEvent, ghost: HTMLElement) {
|
||||||
|
this.dndContainer.removeChild(ghost);
|
||||||
|
this.clearGhostStyle(ghost);
|
||||||
|
}
|
||||||
|
|
||||||
|
over(e: DragEvent, ghost: HTMLElement) {
|
||||||
|
const {isMobile} = this.store;
|
||||||
|
const colTarget = (e.target as HTMLElement).closest('[role="flex-col"]');
|
||||||
|
const wrapper = this.dndContainer;
|
||||||
|
const elemSchema = this.region.schema;
|
||||||
|
const {x: wx, y: wy} = wrapper.getBoundingClientRect();
|
||||||
|
const list: Array<any> = Array.isArray(elemSchema) ? elemSchema : [];
|
||||||
|
this.clearGhostStyle(ghost);
|
||||||
|
|
||||||
|
if (colTarget && list.length) {
|
||||||
|
const {width, height, x, y} = colTarget.getBoundingClientRect();
|
||||||
|
const cx = e.clientX;
|
||||||
|
const cy = e.clientY;
|
||||||
|
const w = width / 8;
|
||||||
|
const h = height / 2;
|
||||||
|
|
||||||
|
const target = this.getTarget(colTarget);
|
||||||
|
const targetId = target.getAttribute('data-editor-id')!;
|
||||||
|
const targetIndex = findIndex(
|
||||||
|
list,
|
||||||
|
(item: any) => item.$$id === targetId
|
||||||
|
);
|
||||||
|
const targetRow = list[targetIndex].row;
|
||||||
|
const targetRowLen = list.filter(
|
||||||
|
(item: any) => item.row === targetRow
|
||||||
|
).length;
|
||||||
|
// 是否可以插入到左右
|
||||||
|
const canRL =
|
||||||
|
this.dragId !== targetId && // 拖拽和目标不能是同一个元素,才能插入到左右
|
||||||
|
this.dragNode?.$$dragMode !== 'hv' &&
|
||||||
|
list[targetIndex]?.$$dragMode !== 'hv' && // 如果拖拽元素和目标元素的拖拽模式不能是垂直,才能插入到左右
|
||||||
|
(targetRowLen < this.maxRolLength ||
|
||||||
|
this.dragNode?.row === targetRow) && // 如果当前行的元素个数小于最大行长度,或者拖拽的元素就在当前行,才能插入到当前行
|
||||||
|
!isMobile; // 移动端不支持左右拖拽
|
||||||
|
|
||||||
|
if (cx < x + w && canRL) {
|
||||||
|
ghost.classList.add(`ae-${className}-left`);
|
||||||
|
ghost.style.left = x - wx + 'px';
|
||||||
|
ghost.style.top = y - wy + 'px';
|
||||||
|
ghost.style.height = height + 'px';
|
||||||
|
this.dropBeforeId = targetId;
|
||||||
|
this.position = 'left';
|
||||||
|
} else if (cx > x + 7 * w && canRL) {
|
||||||
|
ghost.classList.add(`ae-${className}-right`);
|
||||||
|
ghost.style.left = x - wx + width + 'px';
|
||||||
|
ghost.style.top = y - wy + 'px';
|
||||||
|
ghost.style.height = height + 'px';
|
||||||
|
this.dropBeforeId =
|
||||||
|
list[
|
||||||
|
list[targetIndex + 1]?.$$id === this.dragId
|
||||||
|
? targetIndex + 2
|
||||||
|
: targetIndex + 1
|
||||||
|
]?.$$id;
|
||||||
|
this.position = 'right';
|
||||||
|
} else if (cy < y + h) {
|
||||||
|
// 移动端,独占一行的元素不能插入到一行的中间
|
||||||
|
if (
|
||||||
|
this.store.isMobile &&
|
||||||
|
(this.dragNode?.$$dragMode !== 'hv' ||
|
||||||
|
list[targetIndex]?.$$dragMode !== 'hv') &&
|
||||||
|
list[targetIndex].row === list[targetIndex - 1]?.row
|
||||||
|
) {
|
||||||
|
delete this.position;
|
||||||
|
delete this.dropBeforeId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ghost.classList.add(`ae-${className}-top`);
|
||||||
|
ghost.style.width = '100%';
|
||||||
|
ghost.style.top = y - wy + 'px';
|
||||||
|
|
||||||
|
if (this.store.isMobile) {
|
||||||
|
this.dropBeforeId = targetId;
|
||||||
|
} else {
|
||||||
|
const beforeIndex = findIndex(
|
||||||
|
list,
|
||||||
|
(item: any) => item.row === targetRow
|
||||||
|
);
|
||||||
|
const index =
|
||||||
|
list[beforeIndex]?.$$id === this.dragId
|
||||||
|
? beforeIndex + 1
|
||||||
|
: beforeIndex;
|
||||||
|
this.dropBeforeId = list[index]?.$$id;
|
||||||
|
}
|
||||||
|
this.position = 'top';
|
||||||
|
} else {
|
||||||
|
// 移动端,独占一行的元素不能插入到一行的中间
|
||||||
|
if (
|
||||||
|
this.store.isMobile &&
|
||||||
|
(this.dragNode?.$$dragMode !== 'hv' ||
|
||||||
|
list[targetIndex]?.$$dragMode !== 'hv') &&
|
||||||
|
list[targetIndex].row === list[targetIndex + 1]?.row
|
||||||
|
) {
|
||||||
|
delete this.position;
|
||||||
|
delete this.dropBeforeId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ghost.classList.add(`ae-${className}-bottom`);
|
||||||
|
ghost.style.width = '100%';
|
||||||
|
ghost.style.top = y - wy + height + 'px';
|
||||||
|
if (this.store.isMobile) {
|
||||||
|
this.dropBeforeId = list[targetIndex + 1]?.$$id;
|
||||||
|
} else {
|
||||||
|
const lastIndex = findLastIndex(
|
||||||
|
list,
|
||||||
|
(item: any) => item.row === targetRow
|
||||||
|
);
|
||||||
|
const index =
|
||||||
|
list[lastIndex + 1]?.$$id === this.dragId
|
||||||
|
? lastIndex + 2
|
||||||
|
: lastIndex + 1;
|
||||||
|
this.dropBeforeId = list[index]?.$$id;
|
||||||
|
}
|
||||||
|
this.position = 'bottom';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dropBeforeId = undefined;
|
||||||
|
if (list.length) {
|
||||||
|
const rows = wrapper.querySelectorAll('[role="flex-row"]');
|
||||||
|
const lastRow = rows[rows.length - 1];
|
||||||
|
const {y, height} = lastRow.getBoundingClientRect();
|
||||||
|
ghost.classList.add(`ae-${className}-bottom`);
|
||||||
|
ghost.style.width = '100%';
|
||||||
|
ghost.style.top = y - wy + height + 'px';
|
||||||
|
}
|
||||||
|
this.position = 'bottom';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearGhostStyle(ghost: HTMLElement) {
|
||||||
|
// 清除ghost的样式
|
||||||
|
ghost.style.left = '';
|
||||||
|
ghost.style.top = '';
|
||||||
|
ghost.style.right = '';
|
||||||
|
ghost.style.bottom = '';
|
||||||
|
ghost.style.width = '';
|
||||||
|
ghost.style.height = '';
|
||||||
|
ghost.classList.remove(`ae-${className}-left`);
|
||||||
|
ghost.classList.remove(`ae-${className}-right`);
|
||||||
|
ghost.classList.remove(`ae-${className}-top`);
|
||||||
|
ghost.classList.remove(`ae-${className}-bottom`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回个相对位置,如果没有数据会插入到结尾。
|
||||||
|
*/
|
||||||
|
getDropBeforeId() {
|
||||||
|
return this.dropBeforeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当时拖动到了哪个节点上面。
|
||||||
|
*/
|
||||||
|
getTarget(col: Element | null) {
|
||||||
|
let target = col?.querySelector('[data-editor-id]') as HTMLElement;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取区域的直接孩子,因为有时候会在孩子的孩子里面。
|
||||||
|
* 但是插入 ghost 的相对位置,insertBefore 只能是当前孩子。
|
||||||
|
* @param dom
|
||||||
|
* @param descend
|
||||||
|
*/
|
||||||
|
getChild(dom: HTMLElement, descend: HTMLElement) {
|
||||||
|
let child = descend;
|
||||||
|
|
||||||
|
while (child) {
|
||||||
|
if (child.parentElement === dom) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
child = child.parentElement!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁
|
||||||
|
*/
|
||||||
|
dispose() {
|
||||||
|
delete this.dropBeforeId;
|
||||||
|
delete this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDropPosition() {
|
||||||
|
return this.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否中断 drop 事件
|
||||||
|
interruptionDrop() {
|
||||||
|
// 如果没有 dropBeforeId 和 position,说明没有拖拽到任何元素上,中断 drop 事件
|
||||||
|
if (!this.dropBeforeId && !this.position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -9,10 +9,16 @@ import {EditorManager} from '../manager';
|
|||||||
import {DragEventContext, SubRendererInfo} from '../plugin';
|
import {DragEventContext, SubRendererInfo} from '../plugin';
|
||||||
import {EditorStoreType} from '../store/editor';
|
import {EditorStoreType} from '../store/editor';
|
||||||
import {EditorNodeType} from '../store/node';
|
import {EditorNodeType} from '../store/node';
|
||||||
import {autobind, reactionWithOldValue, unitFormula} from '../util';
|
import {
|
||||||
|
JSONGetById,
|
||||||
|
autobind,
|
||||||
|
reactionWithOldValue,
|
||||||
|
unitFormula
|
||||||
|
} from '../util';
|
||||||
import {DefaultDNDMode} from './default';
|
import {DefaultDNDMode} from './default';
|
||||||
import {DNDModeInterface} from './interface';
|
import {DNDModeInterface} from './interface';
|
||||||
import {PositionHDNDMode} from './position-h';
|
import {PositionHDNDMode} from './position-h';
|
||||||
|
import {FlexDNDMode} from './flex';
|
||||||
|
|
||||||
const toastWarning = debounce(msg => {
|
const toastWarning = debounce(msg => {
|
||||||
toast.warning(msg);
|
toast.warning(msg);
|
||||||
@ -175,15 +181,23 @@ export class EditorDNDManager {
|
|||||||
return this.dndMode || null;
|
return this.dndMode || null;
|
||||||
}
|
}
|
||||||
const mode = region.regionInfo?.dndMode;
|
const mode = region.regionInfo?.dndMode;
|
||||||
|
const regionNode = JSONGetById(this.store.schema, region.id);
|
||||||
let Klass: new (
|
let Klass: new (
|
||||||
dnd: EditorDNDManager,
|
dnd: EditorDNDManager,
|
||||||
region: EditorNodeType
|
region: EditorNodeType,
|
||||||
|
config: any
|
||||||
) => DNDModeInterface = DefaultDNDMode; // todo 根据配置自动实例化不同的。
|
) => DNDModeInterface = DefaultDNDMode; // todo 根据配置自动实例化不同的。
|
||||||
|
|
||||||
if (mode === 'position-h') {
|
if (mode === 'position-h') {
|
||||||
Klass = PositionHDNDMode;
|
Klass = PositionHDNDMode;
|
||||||
}
|
}
|
||||||
this.dndMode = new Klass(this, region);
|
if (typeof mode === 'function') {
|
||||||
|
if (mode(regionNode) === 'flex') {
|
||||||
|
Klass = FlexDNDMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dndMode = new Klass(this, region, {regionNode});
|
||||||
return this.dndMode;
|
return this.dndMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,9 +489,20 @@ export class EditorDNDManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const beforeId = this.dndMode?.getDropBeforeId();
|
const beforeId = this.dndMode?.getDropBeforeId();
|
||||||
|
const position = this.dndMode?.getDropPosition?.();
|
||||||
|
|
||||||
|
// 如果中断 drop 事件,则直接返回
|
||||||
|
if (this.dndMode?.interruptionDrop?.()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (store.dragMode === 'move') {
|
if (store.dragMode === 'move') {
|
||||||
this.manager.move(store.dropId, store.dropRegion, store.dragId, beforeId);
|
this.manager.move(
|
||||||
|
store.dropId,
|
||||||
|
store.dropRegion,
|
||||||
|
store.dragId,
|
||||||
|
beforeId,
|
||||||
|
{position}
|
||||||
|
);
|
||||||
} else if (store.dragMode === 'copy') {
|
} else if (store.dragMode === 'copy') {
|
||||||
let schema = store.dragSchema;
|
let schema = store.dragSchema;
|
||||||
const dropId = store.dropId;
|
const dropId = store.dropId;
|
||||||
@ -494,11 +519,20 @@ export class EditorDNDManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.manager.addChild(dropId, dropRegion, schema, beforeId, subRenderer, {
|
this.manager.addChild(
|
||||||
|
dropId,
|
||||||
|
dropRegion,
|
||||||
|
schema,
|
||||||
|
beforeId,
|
||||||
|
subRenderer,
|
||||||
|
{
|
||||||
id: store.dragId,
|
id: store.dragId,
|
||||||
type: store.dragType,
|
type: store.dragType,
|
||||||
data: store.dragSchema
|
data: store.dragSchema,
|
||||||
});
|
position: position
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
DeleteEventContext,
|
||||||
|
InsertEventContext,
|
||||||
|
MoveEventContext
|
||||||
|
} from '../plugin';
|
||||||
import {EditorNodeType} from '../store/node';
|
import {EditorNodeType} from '../store/node';
|
||||||
import {EditorDNDManager} from './index';
|
import {EditorDNDManager} from './index';
|
||||||
|
|
||||||
@ -17,4 +22,8 @@ export interface DNDModeInterface {
|
|||||||
getDropBeforeId: () => string | undefined;
|
getDropBeforeId: () => string | undefined;
|
||||||
|
|
||||||
dispose: () => void;
|
dispose: () => void;
|
||||||
|
|
||||||
|
getDropPosition?: () => 'top' | 'bottom' | 'left' | 'right' | undefined;
|
||||||
|
|
||||||
|
interruptionDrop?: () => boolean; // 是否中断 drop 事件
|
||||||
}
|
}
|
||||||
|
3
packages/amis-editor-core/src/layout/default.ts
Normal file
3
packages/amis-editor-core/src/layout/default.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import {LayoutInterface} from './interface';
|
||||||
|
|
||||||
|
export default class DefaultLayout implements LayoutInterface {}
|
274
packages/amis-editor-core/src/layout/flex.ts
Normal file
274
packages/amis-editor-core/src/layout/flex.ts
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
import findLastIndex from 'lodash/findLastIndex';
|
||||||
|
import {
|
||||||
|
BaseEventContext,
|
||||||
|
InsertEventContext,
|
||||||
|
MoveEventContext
|
||||||
|
} from '../plugin';
|
||||||
|
import {LayoutInterface} from './interface';
|
||||||
|
import {setDefaultColSize} from '../util';
|
||||||
|
|
||||||
|
export default class FlexLayout implements LayoutInterface {
|
||||||
|
beforeInsert(context: InsertEventContext, store: any) {
|
||||||
|
const region = context.region;
|
||||||
|
const body = [...(context.schema?.[region] || [])];
|
||||||
|
let row = 0;
|
||||||
|
if (body?.length) {
|
||||||
|
const beforeId = context.beforeId;
|
||||||
|
const beforeNodeIndex = body.findIndex(
|
||||||
|
(item: any) => item.$$id === beforeId
|
||||||
|
);
|
||||||
|
const beforeNode = body[beforeNodeIndex] || body[body.length - 1];
|
||||||
|
const beforeRow = beforeNode?.row;
|
||||||
|
const position = context.dragInfo?.position || 'bottom';
|
||||||
|
row = beforeRow; // left、bottom、top使用beforeRow,bottom、top后续行需要加1
|
||||||
|
|
||||||
|
if (position === 'right') {
|
||||||
|
const preNode = body[beforeNodeIndex - 1];
|
||||||
|
// 如果前一个节点的row和beforeRow不一样,需要减1
|
||||||
|
if (preNode && preNode.row !== beforeRow) {
|
||||||
|
row = beforeRow - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (position === 'bottom') {
|
||||||
|
if (beforeNodeIndex < 0) {
|
||||||
|
row = beforeRow + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
data: {
|
||||||
|
...context.data,
|
||||||
|
row
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
...context.schema,
|
||||||
|
[region]: body
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
afterInsert(context: InsertEventContext, store: any) {
|
||||||
|
const {isMobile} = store;
|
||||||
|
const region = context.region;
|
||||||
|
const body = [...(context.schema?.[region] || [])];
|
||||||
|
const position = context.dragInfo?.position || 'bottom';
|
||||||
|
const currentIndex = context.regionList.findIndex(
|
||||||
|
(item: any) => item.$$id === context.data.$$id
|
||||||
|
);
|
||||||
|
let regionList = [...context.regionList];
|
||||||
|
if (position === 'top' || position === 'bottom') {
|
||||||
|
if (isMobile) {
|
||||||
|
// const currentRow = regionList[currentIndex].row;
|
||||||
|
const preBeforeIndex = body.findIndex(
|
||||||
|
(item: any) => item.$$id === context.beforeId
|
||||||
|
);
|
||||||
|
const preBeforeRow = body[preBeforeIndex]?.row;
|
||||||
|
// 插入到了一行最后一个元素的后边,所以该元素独占用一行,后续元素的row都加1
|
||||||
|
if (preBeforeRow !== body[preBeforeIndex - 1]?.row) {
|
||||||
|
for (let i = currentIndex + 1; i < regionList.length; i++) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: regionList[i].row + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 插入到了一行的中间,这一行的最后一个元素的row加1,后续元素的row都加1
|
||||||
|
let lastIndex = findLastIndex(
|
||||||
|
regionList,
|
||||||
|
(item: any) => item.row === preBeforeRow
|
||||||
|
);
|
||||||
|
lastIndex = lastIndex === -1 ? currentIndex + 1 : lastIndex;
|
||||||
|
for (let i = lastIndex; i < regionList.length; i++) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: regionList[i].row + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = currentIndex + 1; i < regionList.length; i++) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: regionList[i].row + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.data.$$defaultColSize &&
|
||||||
|
(regionList[currentIndex].colSize = context.data.$$defaultColSize);
|
||||||
|
} else {
|
||||||
|
regionList = regionList.map((item: any) => {
|
||||||
|
if (item.row === context.data.row) {
|
||||||
|
item = {
|
||||||
|
...item,
|
||||||
|
colSize: 'auto'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
regionList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
afterMove(context: MoveEventContext, store: any) {
|
||||||
|
const {isMobile} = store;
|
||||||
|
const position = context.dragInfo?.position;
|
||||||
|
const region = context.region;
|
||||||
|
const body = [...(context.schema?.[region] || [])];
|
||||||
|
const preCurrentIndex = body.findIndex(
|
||||||
|
(item: any) => item.$$id === context.sourceId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 如果是最后一个元素往自己的上边移动,不做处理
|
||||||
|
if (
|
||||||
|
position === 'top' &&
|
||||||
|
preCurrentIndex === body.length - 1 &&
|
||||||
|
!context.beforeId
|
||||||
|
) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
let regionList = [...context.regionList];
|
||||||
|
const currentIndex = regionList.findIndex(
|
||||||
|
(item: any) => item.$$id === context.sourceId
|
||||||
|
);
|
||||||
|
// 如果移动的元素是整行,则需要将后续的元素的row减1
|
||||||
|
const preCurrentRow = body[preCurrentIndex].row;
|
||||||
|
if (body.filter((item: any) => item.row === preCurrentRow).length === 1) {
|
||||||
|
for (let i = preCurrentIndex; i < regionList.length; i++) {
|
||||||
|
if (regionList[i].row > preCurrentRow) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: regionList[i].row - 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeIndex = regionList.findIndex(
|
||||||
|
(item: any) => item.$$id === context.beforeId
|
||||||
|
);
|
||||||
|
const beforeNode =
|
||||||
|
regionList[beforeIndex] || regionList[regionList.length - 2];
|
||||||
|
const beforeRow = beforeNode?.row;
|
||||||
|
|
||||||
|
if (typeof beforeRow !== 'number') {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = beforeRow;
|
||||||
|
|
||||||
|
if (position === 'right') {
|
||||||
|
const preNode = regionList[beforeIndex - 2];
|
||||||
|
if (preNode && preNode.row !== beforeRow) {
|
||||||
|
row = beforeRow - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (position === 'bottom') {
|
||||||
|
if (beforeIndex < 0) {
|
||||||
|
row = beforeRow + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position === 'top' || position === 'bottom') {
|
||||||
|
if (isMobile) {
|
||||||
|
const preBeforeIndex = body.findIndex(
|
||||||
|
(item: any) => item.$$id === context.beforeId
|
||||||
|
);
|
||||||
|
// 独占一行
|
||||||
|
if (beforeRow !== body[preBeforeIndex - 1]?.row) {
|
||||||
|
for (let i = currentIndex + 1; i < regionList.length; i++) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: regionList[i].row + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const lastIndex = findLastIndex(
|
||||||
|
regionList,
|
||||||
|
(item: any) => item.row === beforeRow
|
||||||
|
);
|
||||||
|
for (let i = lastIndex; i < regionList.length; i++) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: regionList[i].row + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = currentIndex + 1; i < regionList.length; i++) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: regionList[i].row + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regionList[currentIndex] = {
|
||||||
|
...regionList[currentIndex],
|
||||||
|
row
|
||||||
|
};
|
||||||
|
|
||||||
|
regionList = setDefaultColSize(regionList, row, preCurrentRow);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
regionList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
afterDelete(context: BaseEventContext) {
|
||||||
|
let regionList = [...context.regionList];
|
||||||
|
let preRow = -1;
|
||||||
|
for (let i = 0; i < regionList.length; i++) {
|
||||||
|
const row = regionList[i].row;
|
||||||
|
if (row - preRow >= 2) {
|
||||||
|
regionList[i] = {
|
||||||
|
...regionList[i],
|
||||||
|
row: row - 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (regionList[i + 1]?.row !== row) {
|
||||||
|
preRow = regionList[i].row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regionList = setDefaultColSize(regionList, -1, preRow);
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
regionList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
afterMoveDown(context: BaseEventContext) {
|
||||||
|
const regionList = [...context.regionList];
|
||||||
|
const sourceId = context.sourceId;
|
||||||
|
const currentIndex = regionList.findIndex(n => n.$$id === sourceId);
|
||||||
|
const currentItem = regionList[currentIndex];
|
||||||
|
const changeItem = regionList[currentIndex - 1];
|
||||||
|
const tempRow = currentItem.row;
|
||||||
|
currentItem.row = changeItem.row;
|
||||||
|
changeItem.row = tempRow;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
regionList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
afterMoveUp(context: BaseEventContext) {
|
||||||
|
const regionList = [...context.regionList];
|
||||||
|
const sourceId = context.sourceId;
|
||||||
|
const currentIndex = regionList.findIndex(n => n.$$id === sourceId);
|
||||||
|
const currentItem = regionList[currentIndex];
|
||||||
|
const changeItem = regionList[currentIndex + 1];
|
||||||
|
const tempRow = currentItem.row;
|
||||||
|
currentItem.row = changeItem.row;
|
||||||
|
changeItem.row = tempRow;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...context,
|
||||||
|
regionList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
24
packages/amis-editor-core/src/layout/index.ts
Normal file
24
packages/amis-editor-core/src/layout/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {EditorNodeType} from '../store/node';
|
||||||
|
import {JSONGetById} from '../util';
|
||||||
|
import DefaultLayout from './default';
|
||||||
|
import FlexLayout from './flex';
|
||||||
|
import {LayoutInterface} from './interface';
|
||||||
|
|
||||||
|
export default function getLayoutInstance(
|
||||||
|
schema: any,
|
||||||
|
region: EditorNodeType
|
||||||
|
): LayoutInterface {
|
||||||
|
if (!region) {
|
||||||
|
return new DefaultLayout();
|
||||||
|
}
|
||||||
|
const mode = region?.regionInfo?.dndMode;
|
||||||
|
const regionNode = JSONGetById(schema, region?.id);
|
||||||
|
let Klass = DefaultLayout;
|
||||||
|
if (typeof mode === 'function') {
|
||||||
|
if (mode(regionNode) === 'flex') {
|
||||||
|
Klass = FlexLayout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Klass();
|
||||||
|
}
|
24
packages/amis-editor-core/src/layout/interface.ts
Normal file
24
packages/amis-editor-core/src/layout/interface.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
BaseEventContext,
|
||||||
|
InsertEventContext,
|
||||||
|
MoveEventContext
|
||||||
|
} from '../plugin';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每种布局模式在数据变更前后的处理逻辑
|
||||||
|
*/
|
||||||
|
export interface LayoutInterface {
|
||||||
|
beforeInsert?: (
|
||||||
|
context: InsertEventContext,
|
||||||
|
store: any
|
||||||
|
) => InsertEventContext;
|
||||||
|
afterInsert?: (context: InsertEventContext, store: any) => InsertEventContext;
|
||||||
|
beforeMove?: (context: MoveEventContext, store: any) => MoveEventContext;
|
||||||
|
afterMove?: (context: MoveEventContext, store: any) => MoveEventContext;
|
||||||
|
beforeDelete?: (context: BaseEventContext, store: any) => BaseEventContext;
|
||||||
|
afterDelete?: (context: BaseEventContext, store: any) => BaseEventContext;
|
||||||
|
beforeMoveDown?: (context: BaseEventContext, store: any) => BaseEventContext;
|
||||||
|
afterMoveDown?: (context: BaseEventContext, store: any) => BaseEventContext;
|
||||||
|
beforeMoveUp?: (context: BaseEventContext, store: any) => BaseEventContext;
|
||||||
|
afterMoveUp?: (context: BaseEventContext, store: any) => BaseEventContext;
|
||||||
|
}
|
@ -56,7 +56,8 @@ import {
|
|||||||
isLayoutPlugin,
|
isLayoutPlugin,
|
||||||
JSONPipeOut,
|
JSONPipeOut,
|
||||||
scrollToActive,
|
scrollToActive,
|
||||||
JSONPipeIn
|
JSONPipeIn,
|
||||||
|
JSONGetById
|
||||||
} from './util';
|
} from './util';
|
||||||
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
|
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
|
||||||
import {env} from './env';
|
import {env} from './env';
|
||||||
@ -961,10 +962,6 @@ export class EditorManager {
|
|||||||
// 当前节点是布局类容器节点
|
// 当前节点是布局类容器节点
|
||||||
regionNodeId = curActiveId;
|
regionNodeId = curActiveId;
|
||||||
regionNodeRegion = 'items';
|
regionNodeRegion = 'items';
|
||||||
} else if (node.schema.fields && node.schema.type === 'doc-entity') {
|
|
||||||
// 当前节点是表单视图
|
|
||||||
regionNodeId = curActiveId;
|
|
||||||
regionNodeRegion = 'fields';
|
|
||||||
} else if (node.schema.body) {
|
} else if (node.schema.body) {
|
||||||
// 当前节点是容器节点
|
// 当前节点是容器节点
|
||||||
regionNodeId = curActiveId;
|
regionNodeId = curActiveId;
|
||||||
@ -1451,12 +1448,13 @@ export class EditorManager {
|
|||||||
sourceId: node.id,
|
sourceId: node.id,
|
||||||
direction: 'up',
|
direction: 'up',
|
||||||
beforeId: node.prevSibling?.id,
|
beforeId: node.prevSibling?.id,
|
||||||
region: regionNode.region
|
region: regionNode.region,
|
||||||
|
regionNode: regionNode
|
||||||
};
|
};
|
||||||
|
|
||||||
const event = this.trigger('before-move', context);
|
const event = this.trigger('before-move', context);
|
||||||
if (!event.prevented) {
|
if (!event.prevented) {
|
||||||
store.moveUp(node.id);
|
store.moveUp(context);
|
||||||
// this.buildToolbars();
|
// this.buildToolbars();
|
||||||
this.trigger('after-move', context);
|
this.trigger('after-move', context);
|
||||||
this.trigger('after-update', context);
|
this.trigger('after-update', context);
|
||||||
@ -1482,12 +1480,13 @@ export class EditorManager {
|
|||||||
sourceId: node.id,
|
sourceId: node.id,
|
||||||
direction: 'down',
|
direction: 'down',
|
||||||
beforeId: node.nextSibling?.nextSibling?.id,
|
beforeId: node.nextSibling?.nextSibling?.id,
|
||||||
region: regionNode.region
|
region: regionNode.region,
|
||||||
|
regionNode: regionNode
|
||||||
};
|
};
|
||||||
|
|
||||||
const event = this.trigger('before-move', context);
|
const event = this.trigger('before-move', context);
|
||||||
if (!event.prevented) {
|
if (!event.prevented) {
|
||||||
store.moveDown(node.id);
|
store.moveDown(context);
|
||||||
// this.buildToolbars();
|
// this.buildToolbars();
|
||||||
this.trigger('after-move', context);
|
this.trigger('after-move', context);
|
||||||
this.trigger('after-update', context);
|
this.trigger('after-update', context);
|
||||||
@ -1512,8 +1511,7 @@ export class EditorManager {
|
|||||||
if (!event.prevented) {
|
if (!event.prevented) {
|
||||||
Array.isArray(context.data) && context.data.length
|
Array.isArray(context.data) && context.data.length
|
||||||
? this.store.delMulti(context.data)
|
? this.store.delMulti(context.data)
|
||||||
: this.store.del(id);
|
: this.store.del(context);
|
||||||
|
|
||||||
this.trigger('after-delete', context);
|
this.trigger('after-delete', context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1617,6 +1615,7 @@ export class EditorManager {
|
|||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
data: any;
|
data: any;
|
||||||
|
position?: string;
|
||||||
},
|
},
|
||||||
reGenerateId?: boolean
|
reGenerateId?: boolean
|
||||||
): any | null {
|
): any | null {
|
||||||
@ -1665,7 +1664,8 @@ export class EditorManager {
|
|||||||
id: string,
|
id: string,
|
||||||
region: string,
|
region: string,
|
||||||
sourceId: string,
|
sourceId: string,
|
||||||
beforeId?: string
|
beforeId?: string,
|
||||||
|
dragInfo?: any
|
||||||
): boolean {
|
): boolean {
|
||||||
const store = this.store;
|
const store = this.store;
|
||||||
|
|
||||||
@ -1673,7 +1673,8 @@ export class EditorManager {
|
|||||||
...this.buildEventContext(id),
|
...this.buildEventContext(id),
|
||||||
beforeId,
|
beforeId,
|
||||||
region: region,
|
region: region,
|
||||||
sourceId
|
sourceId,
|
||||||
|
dragInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
const event = this.trigger('before-move', context);
|
const event = this.trigger('before-move', context);
|
||||||
|
@ -114,7 +114,9 @@ export interface RegionConfig {
|
|||||||
| 'default'
|
| 'default'
|
||||||
| 'position-h'
|
| 'position-h'
|
||||||
| 'position-v'
|
| 'position-v'
|
||||||
| (new (dnd: EditorDNDManager) => DNDModeInterface);
|
| 'flex'
|
||||||
|
// | (new (dnd: EditorDNDManager) => DNDModeInterface)
|
||||||
|
| ((node: any) => string | undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可以用来判断是否允许拖入当前节点。
|
* 可以用来判断是否允许拖入当前节点。
|
||||||
@ -213,6 +215,11 @@ export interface RendererInfo extends RendererScaffoldInfo {
|
|||||||
*/
|
*/
|
||||||
regions?: Array<RegionConfig>;
|
regions?: Array<RegionConfig>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中不需要高亮
|
||||||
|
*/
|
||||||
|
notHighlight?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 哪些容器属性需要自动转成数组的。如果不配置默认就从 regions 里面读取。
|
* 哪些容器属性需要自动转成数组的。如果不配置默认就从 regions 里面读取。
|
||||||
*/
|
*/
|
||||||
@ -534,6 +541,7 @@ export interface InsertEventContext extends BaseEventContext {
|
|||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
data: any;
|
data: any;
|
||||||
|
position?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -820,6 +828,11 @@ export interface PluginInterface
|
|||||||
*/
|
*/
|
||||||
async?: AsyncLayerOptions;
|
async?: AsyncLayerOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拖拽模式
|
||||||
|
*/
|
||||||
|
dragMode?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 有数据域的容器,可以为子组件提供读取的字段绑定页面
|
* 有数据域的容器,可以为子组件提供读取的字段绑定页面
|
||||||
*/
|
*/
|
||||||
|
@ -122,7 +122,8 @@ export class BasicToolbarPlugin extends BasePlugin {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!host?.memberImmutable(regionNode.region) &&
|
!host?.memberImmutable(regionNode.region) &&
|
||||||
store.panels.some(Panel => Panel.key === 'renderers')
|
store.panels.some(Panel => Panel.key === 'renderers') &&
|
||||||
|
store.toolbarMode === 'default'
|
||||||
) {
|
) {
|
||||||
const nextId = parent[idx + 1]?.$$id;
|
const nextId = parent[idx + 1]?.$$id;
|
||||||
|
|
||||||
@ -206,7 +207,7 @@ export class BasicToolbarPlugin extends BasePlugin {
|
|||||||
onClick: this.manager.del.bind(this.manager, id)
|
onClick: this.manager.del.bind(this.manager, id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (store.toolbarMode === 'default') {
|
||||||
toolbars.push({
|
toolbars.push({
|
||||||
id: 'more',
|
id: 'more',
|
||||||
iconSvg: 'more-btn',
|
iconSvg: 'more-btn',
|
||||||
@ -236,6 +237,7 @@ export class BasicToolbarPlugin extends BasePlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (info.scaffoldForm?.canRebuild ?? info.plugin.scaffoldForm?.canRebuild) {
|
if (info.scaffoldForm?.canRebuild ?? info.plugin.scaffoldForm?.canRebuild) {
|
||||||
toolbars.push({
|
toolbars.push({
|
||||||
@ -263,6 +265,7 @@ export class BasicToolbarPlugin extends BasePlugin {
|
|||||||
|
|
||||||
if (selections.length) {
|
if (selections.length) {
|
||||||
// 多选时的右键菜单
|
// 多选时的右键菜单
|
||||||
|
if (store.toolbarMode === 'default') {
|
||||||
menus.push({
|
menus.push({
|
||||||
id: 'copy',
|
id: 'copy',
|
||||||
label: '重复一份',
|
label: '重复一份',
|
||||||
@ -270,6 +273,7 @@ export class BasicToolbarPlugin extends BasePlugin {
|
|||||||
disabled: selections.some(item => !item.node.duplicatable),
|
disabled: selections.some(item => !item.node.duplicatable),
|
||||||
onSelect: () => manager.duplicate(selections.map(item => item.id))
|
onSelect: () => manager.duplicate(selections.map(item => item.id))
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
menus.push({
|
menus.push({
|
||||||
id: 'unselect',
|
id: 'unselect',
|
||||||
@ -320,6 +324,9 @@ export class BasicToolbarPlugin extends BasePlugin {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (store.toolbarMode === 'mini') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
menus.push({
|
menus.push({
|
||||||
id: 'select',
|
id: 'select',
|
||||||
label: `选中${first.label}`,
|
label: `选中${first.label}`,
|
||||||
|
@ -32,7 +32,7 @@ export class DataDebugPlugin extends BasePlugin {
|
|||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
const store = comp.props.store;
|
const store = comp.props.store;
|
||||||
|
if (store.toolbarMode === 'default') {
|
||||||
toolbars.push({
|
toolbars.push({
|
||||||
icon: 'fa fa-bug',
|
icon: 'fa fa-bug',
|
||||||
order: -1000,
|
order: -1000,
|
||||||
@ -41,6 +41,7 @@ export class DataDebugPlugin extends BasePlugin {
|
|||||||
onClick: () => this.openDebugForm(comp.props.data)
|
onClick: () => this.openDebugForm(comp.props.data)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dataViewer = {
|
dataViewer = {
|
||||||
type: 'json',
|
type: 'json',
|
||||||
|
@ -34,7 +34,9 @@ import {
|
|||||||
PanelItem,
|
PanelItem,
|
||||||
MoveEventContext,
|
MoveEventContext,
|
||||||
ScaffoldForm,
|
ScaffoldForm,
|
||||||
PopOverForm
|
PopOverForm,
|
||||||
|
DeleteEventContext,
|
||||||
|
BaseEventContext
|
||||||
} from '../plugin';
|
} from '../plugin';
|
||||||
import {
|
import {
|
||||||
JSONDuplicate,
|
JSONDuplicate,
|
||||||
@ -59,6 +61,7 @@ import {matchSorter} from 'match-sorter';
|
|||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import type {DialogSchema} from '../../../amis/src/renderers/Dialog';
|
import type {DialogSchema} from '../../../amis/src/renderers/Dialog';
|
||||||
import type {DrawerSchema} from '../../../amis/src/renderers/Drawer';
|
import type {DrawerSchema} from '../../../amis/src/renderers/Drawer';
|
||||||
|
import getLayoutInstance from '../layout';
|
||||||
|
|
||||||
export interface SchemaHistory {
|
export interface SchemaHistory {
|
||||||
versionId: number;
|
versionId: number;
|
||||||
@ -152,6 +155,8 @@ export const MainStore = types
|
|||||||
label: 'Root'
|
label: 'Root'
|
||||||
}),
|
}),
|
||||||
theme: 'cxd', // 主题,默认cxd主题
|
theme: 'cxd', // 主题,默认cxd主题
|
||||||
|
toolbarMode: 'default', // 工具栏模式,默认default,mini模式没有更多、前后插入组件、上下文数据、重复一份、合成一行、右键功能
|
||||||
|
noDialog: false, // 不需要弹框功能
|
||||||
hoverId: '',
|
hoverId: '',
|
||||||
hoverRegion: '',
|
hoverRegion: '',
|
||||||
activeId: '',
|
activeId: '',
|
||||||
@ -1236,6 +1241,11 @@ export const MainStore = types
|
|||||||
// 显然有错误。
|
// 显然有错误。
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const node = self.getNodeById(id, region);
|
||||||
|
const LayoutInstance = getLayoutInstance(self.schema, node!);
|
||||||
|
const {beforeInsert, afterInsert} = LayoutInstance;
|
||||||
|
|
||||||
|
beforeInsert && (event.context = beforeInsert(event.context, self));
|
||||||
|
|
||||||
const child = JSONPipeIn(event.context.data);
|
const child = JSONPipeIn(event.context.data);
|
||||||
|
|
||||||
@ -1267,13 +1277,21 @@ export const MainStore = types
|
|||||||
arr.push(child);
|
arr.push(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event.context.data = child;
|
||||||
|
event.context.regionList = arr;
|
||||||
|
afterInsert && (event.context = afterInsert(event.context, self));
|
||||||
|
|
||||||
this.traceableSetSchema(
|
this.traceableSetSchema(
|
||||||
JSONUpdate(self.schema, id, {
|
JSONUpdate(self.schema, id, {
|
||||||
[region]: arr
|
[region]: event.context.regionList
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
event.context.data = child;
|
child?.$$id &&
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setActiveId(child.$$id);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1285,12 +1303,17 @@ export const MainStore = types
|
|||||||
if (context.sourceId === context.beforeId) {
|
if (context.sourceId === context.beforeId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const region = context.region;
|
||||||
|
|
||||||
|
const node = self.getNodeById(context.id, region);
|
||||||
|
const LayoutInstance = getLayoutInstance(self.schema, node!);
|
||||||
|
const {beforeMove, afterMove} = LayoutInstance;
|
||||||
|
|
||||||
|
beforeMove && (event.context = beforeMove(event.context, self));
|
||||||
|
|
||||||
const source = JSONGetById(schema, context.sourceId);
|
const source = JSONGetById(schema, context.sourceId);
|
||||||
schema = JSONDelete(schema, context.sourceId, undefined, true);
|
schema = JSONDelete(schema, context.sourceId, undefined, true);
|
||||||
|
|
||||||
const region = context.region;
|
|
||||||
|
|
||||||
const json = JSONGetById(schema, context.id);
|
const json = JSONGetById(schema, context.id);
|
||||||
let origin = json[region];
|
let origin = json[region];
|
||||||
origin = Array.isArray(origin)
|
origin = Array.isArray(origin)
|
||||||
@ -1300,11 +1323,10 @@ export const MainStore = types
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (context.beforeId) {
|
if (context.beforeId) {
|
||||||
const idx = findIndex(
|
let idx = findIndex(
|
||||||
origin,
|
origin,
|
||||||
(item: any) => item.$$id === context.beforeId
|
(item: any) => item.$$id === context.beforeId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!~idx) {
|
if (!~idx) {
|
||||||
throw new Error('位置错误,目标位置没有找到');
|
throw new Error('位置错误,目标位置没有找到');
|
||||||
}
|
}
|
||||||
@ -1313,9 +1335,12 @@ export const MainStore = types
|
|||||||
origin.push(source);
|
origin.push(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event.context.regionList = origin;
|
||||||
|
afterMove && (event.context = afterMove(event.context, self));
|
||||||
|
|
||||||
this.traceableSetSchema(
|
this.traceableSetSchema(
|
||||||
JSONUpdate(schema, context.id, {
|
JSONUpdate(schema, context.id, {
|
||||||
[region]: origin
|
[region]: event.context.regionList
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -1603,35 +1628,84 @@ export const MainStore = types
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
moveUp(id: string) {
|
moveUp(context: BaseEventContext) {
|
||||||
if (!id) {
|
const {sourceId, regionNode, region, id} = context;
|
||||||
|
if (!sourceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const schema = JSONMoveUpById(self.schema, sourceId);
|
||||||
|
const LayoutInstance = getLayoutInstance(self.schema, regionNode);
|
||||||
|
|
||||||
this.traceableSetSchema(JSONMoveUpById(self.schema, id));
|
if (LayoutInstance.afterMoveUp) {
|
||||||
|
const parent = JSONGetById(schema, id);
|
||||||
|
let regionList = parent[region];
|
||||||
|
context.regionList = regionList;
|
||||||
|
context = LayoutInstance.afterMoveUp(context, self);
|
||||||
|
this.traceableSetSchema(
|
||||||
|
JSONUpdate(schema, id, {
|
||||||
|
[region]: context.regionList
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.traceableSetSchema(schema);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
moveDown(id: string) {
|
moveDown(context: BaseEventContext) {
|
||||||
if (!id) {
|
const {sourceId, regionNode, region, id} = context;
|
||||||
|
if (!sourceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const schema = JSONMoveDownById(self.schema, sourceId);
|
||||||
|
const LayoutInstance = getLayoutInstance(self.schema, regionNode);
|
||||||
|
|
||||||
this.traceableSetSchema(JSONMoveDownById(self.schema, id));
|
if (LayoutInstance.afterMoveDown) {
|
||||||
|
const parent = JSONGetById(schema, id);
|
||||||
|
let regionList = parent[region];
|
||||||
|
context.regionList = regionList;
|
||||||
|
context = LayoutInstance.afterMoveDown(context, self);
|
||||||
|
this.traceableSetSchema(
|
||||||
|
JSONUpdate(schema, id, {
|
||||||
|
[region]: context.regionList
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.traceableSetSchema(schema);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
del(id: string) {
|
del(context: DeleteEventContext) {
|
||||||
|
const id = context.id;
|
||||||
if (id === self.activeId) {
|
if (id === self.activeId) {
|
||||||
const host = self.getNodeById(id)?.host;
|
const node = self.getNodeById(id);
|
||||||
this.setActiveId(host ? host.id : '');
|
this.setActiveId(node?.parentId || '', node?.parentRegion);
|
||||||
} else if (self.activeId) {
|
} else if (self.activeId) {
|
||||||
const active = JSONGetById(self.schema, id);
|
const active = JSONGetById(self.schema, id);
|
||||||
|
|
||||||
// 如果当前点选的是要删的节点里面的,则改成选中当前要删的上层
|
// 如果当前点选的是要删的节点里面的,则改成选中当前要删的上层
|
||||||
if (JSONGetById(active, self.activeId)) {
|
if (JSONGetById(active, self.activeId)) {
|
||||||
const host = self.getNodeById(id)?.host;
|
const node = self.getNodeById(id);
|
||||||
this.setActiveId(host ? host.id : '');
|
this.setActiveId(node?.parentId || '', node?.parentRegion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.traceableSetSchema(JSONDelete(self.schema, id));
|
|
||||||
|
const schema = JSONDelete(self.schema, id);
|
||||||
|
|
||||||
|
const node = self.getNodeById(id);
|
||||||
|
const LayoutInstance = getLayoutInstance(self.schema, node?.parent);
|
||||||
|
|
||||||
|
if (LayoutInstance.afterDelete && node) {
|
||||||
|
const parent = JSONGetById(schema, node.parentId);
|
||||||
|
let regionList = parent[node.parentRegion];
|
||||||
|
context.regionList = regionList;
|
||||||
|
context = LayoutInstance.afterDelete(context, self);
|
||||||
|
this.traceableSetSchema(
|
||||||
|
JSONUpdate(schema, node.parentId, {
|
||||||
|
[node.parentRegion]: context.regionList
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.traceableSetSchema(schema);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
delMulti(ids: Array<string>) {
|
delMulti(ids: Array<string>) {
|
||||||
|
@ -16,6 +16,7 @@ import isNumber from 'lodash/isNumber';
|
|||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import {EditorModalBody} from './store/editor';
|
import {EditorModalBody} from './store/editor';
|
||||||
|
import {filter} from 'lodash';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
guid,
|
guid,
|
||||||
@ -1578,3 +1579,28 @@ export function mergeDefinitions(
|
|||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setDefaultColSize(
|
||||||
|
regionList: any[],
|
||||||
|
row: number,
|
||||||
|
preRow: number
|
||||||
|
) {
|
||||||
|
const tempList = [...regionList];
|
||||||
|
const preRowNodeLength = filter(tempList, n => n.row === preRow).length;
|
||||||
|
const currentRowNodeLength = filter(tempList, n => n.row === row).length;
|
||||||
|
for (let i = 0; i < tempList.length; i++) {
|
||||||
|
const item = tempList[i];
|
||||||
|
if (item.row === row) {
|
||||||
|
item.colSize = 'auto';
|
||||||
|
}
|
||||||
|
// 原来的行只有一个节点,且有默认宽度,则设置默认宽度
|
||||||
|
if (
|
||||||
|
((preRowNodeLength === 1 && item.row === preRow) ||
|
||||||
|
(currentRowNodeLength === 1 && item.row === row)) &&
|
||||||
|
item.$$defaultColSize
|
||||||
|
) {
|
||||||
|
item.colSize = item.$$defaultColSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempList;
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ export * from 'amis-editor-core';
|
|||||||
export * from './builder';
|
export * from './builder';
|
||||||
import './tpl/index';
|
import './tpl/index';
|
||||||
export * from './plugin';
|
export * from './plugin';
|
||||||
|
export * from './validator';
|
||||||
|
|
||||||
import './renderer/OptionControl';
|
import './renderer/OptionControl';
|
||||||
import './renderer/ValueFormatControl';
|
import './renderer/ValueFormatControl';
|
||||||
@ -16,11 +17,13 @@ import './renderer/TimelineItemControl';
|
|||||||
import './renderer/APIControl';
|
import './renderer/APIControl';
|
||||||
import './renderer/APIAdaptorControl';
|
import './renderer/APIAdaptorControl';
|
||||||
import './renderer/ValidationControl';
|
import './renderer/ValidationControl';
|
||||||
|
import './renderer/ValidateApiControl';
|
||||||
import './renderer/ValidationItem';
|
import './renderer/ValidationItem';
|
||||||
import './renderer/SwitchMoreControl';
|
import './renderer/SwitchMoreControl';
|
||||||
import './renderer/StatusControl';
|
import './renderer/StatusControl';
|
||||||
import './renderer/FormulaControl';
|
import './renderer/FormulaControl';
|
||||||
import './renderer/ExpressionFormulaControl';
|
import './renderer/ExpressionFormulaControl';
|
||||||
|
import './renderer/ConditionFormulaControl';
|
||||||
import './renderer/textarea-formula/TextareaFormulaControl';
|
import './renderer/textarea-formula/TextareaFormulaControl';
|
||||||
import './renderer/TplFormulaControl';
|
import './renderer/TplFormulaControl';
|
||||||
import './renderer/DateShortCutControl';
|
import './renderer/DateShortCutControl';
|
||||||
|
@ -24,7 +24,8 @@ export class DividerPlugin extends BasePlugin {
|
|||||||
description = '用来展示一个分割线,可用来做视觉上的隔离。';
|
description = '用来展示一个分割线,可用来做视觉上的隔离。';
|
||||||
docLink = '/amis/zh-CN/components/divider';
|
docLink = '/amis/zh-CN/components/divider';
|
||||||
scaffold = {
|
scaffold = {
|
||||||
type: 'divider'
|
type: 'divider',
|
||||||
|
$$dragMode: 'hv'
|
||||||
};
|
};
|
||||||
previewSchema: any = {
|
previewSchema: any = {
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
|
@ -20,7 +20,10 @@ import {
|
|||||||
ScaffoldForm,
|
ScaffoldForm,
|
||||||
RegionConfig,
|
RegionConfig,
|
||||||
registerEditorPlugin,
|
registerEditorPlugin,
|
||||||
JSONPipeOut
|
JSONPipeOut,
|
||||||
|
InsertEventContext,
|
||||||
|
MoveEventContext,
|
||||||
|
DeleteEventContext
|
||||||
} from 'amis-editor-core';
|
} from 'amis-editor-core';
|
||||||
import {
|
import {
|
||||||
DSFeatureType,
|
DSFeatureType,
|
||||||
|
@ -268,6 +268,9 @@ export class ItemPlugin extends BasePlugin {
|
|||||||
{id, schema, region, selections}: ContextMenuEventContext,
|
{id, schema, region, selections}: ContextMenuEventContext,
|
||||||
menus: Array<ContextMenuItem>
|
menus: Array<ContextMenuItem>
|
||||||
) {
|
) {
|
||||||
|
if (this.manager.store.toolbarMode === 'mini') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!selections.length || selections.length > 3) {
|
if (!selections.length || selections.length > 3) {
|
||||||
// 单选或者超过3个选中态时直接返回
|
// 单选或者超过3个选中态时直接返回
|
||||||
return;
|
return;
|
||||||
|
204
packages/amis-editor/src/renderer/ConditionFormulaControl.tsx
Normal file
204
packages/amis-editor/src/renderer/ConditionFormulaControl.tsx
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
/**
|
||||||
|
* @file 条件输入框组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {render as renderAmis, autobind, FormControlProps} from 'amis-core';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import {FormItem, Button, PickerContainer, ConditionBuilderFields} from 'amis';
|
||||||
|
import {reaction} from 'mobx';
|
||||||
|
import {getVariables} from 'amis-editor-core';
|
||||||
|
|
||||||
|
interface ConditionFormulaControlProps extends FormControlProps {
|
||||||
|
/**
|
||||||
|
* 用于选择的变量集合,默认为空,用法同ConditionBuilder 条件组合
|
||||||
|
*/
|
||||||
|
fields?: ConditionBuilderFields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否从 amis数据域中取变量集合,默认 true
|
||||||
|
*/
|
||||||
|
requiredDataPropsFields?: boolean;
|
||||||
|
|
||||||
|
[props: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConditionFormulaControlState {
|
||||||
|
formulaPickerValue: string;
|
||||||
|
fields: ConditionBuilderFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把数据域中的变量类型转换成符合ConditionBuilder的type
|
||||||
|
const PropsFieldsMapping: {
|
||||||
|
[props: string]: string;
|
||||||
|
} = {
|
||||||
|
string: 'text',
|
||||||
|
number: 'number',
|
||||||
|
boolean: 'boolean'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ConditionFormulaControl extends React.Component<
|
||||||
|
ConditionFormulaControlProps,
|
||||||
|
ConditionFormulaControlState
|
||||||
|
> {
|
||||||
|
static defaultProps: Partial<ConditionFormulaControlProps> = {
|
||||||
|
requiredDataPropsFields: true
|
||||||
|
};
|
||||||
|
|
||||||
|
isUnmount: boolean;
|
||||||
|
unReaction: any;
|
||||||
|
appLocale: string;
|
||||||
|
appCorpusData: any;
|
||||||
|
|
||||||
|
constructor(props: ConditionFormulaControlProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
fields: [],
|
||||||
|
formulaPickerValue: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
this.initFormulaPickerValue(this.props.value);
|
||||||
|
const editorStore = (window as any).editorStore;
|
||||||
|
this.appLocale = editorStore?.appLocale;
|
||||||
|
this.appCorpusData = editorStore?.appCorpusData;
|
||||||
|
|
||||||
|
this.unReaction = reaction(
|
||||||
|
() => editorStore?.appLocaleState,
|
||||||
|
async () => {
|
||||||
|
this.appLocale = editorStore?.appLocale;
|
||||||
|
this.appCorpusData = editorStore?.appCorpusData;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldsArr = await this.buildFieldsData();
|
||||||
|
this.setState({
|
||||||
|
fields: fieldsArr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidUpdate(prevProps: ConditionFormulaControlProps) {
|
||||||
|
if (prevProps.value !== this.props.value) {
|
||||||
|
this.initFormulaPickerValue(this.props.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.isUnmount = true;
|
||||||
|
this.unReaction?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
async buildFieldsData() {
|
||||||
|
let fieldsArr: ConditionBuilderFields = [];
|
||||||
|
const {requiredDataPropsFields, fields} = this.props;
|
||||||
|
if (requiredDataPropsFields) {
|
||||||
|
const variablesArr = await getVariables(this);
|
||||||
|
|
||||||
|
// 自身字段
|
||||||
|
const selfName = this.props?.data?.name;
|
||||||
|
const vars =
|
||||||
|
variablesArr?.filter((item: any) => item?.label === '组件上下文')?.[0]
|
||||||
|
?.children?.[0]?.children || [];
|
||||||
|
|
||||||
|
fieldsArr = vars
|
||||||
|
.map((item: any) => {
|
||||||
|
if (item && item.type && PropsFieldsMapping[item.type]) {
|
||||||
|
let obj: any = {
|
||||||
|
label: item.label,
|
||||||
|
type: PropsFieldsMapping[item.type],
|
||||||
|
name: item.value
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selfName === item.value) {
|
||||||
|
obj = {
|
||||||
|
...obj,
|
||||||
|
label: item.label + '(self)',
|
||||||
|
disabled: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
?.filter((item: any) => item);
|
||||||
|
}
|
||||||
|
return fieldsArr.concat(fields || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
initFormulaPickerValue(value: string) {
|
||||||
|
this.setState({
|
||||||
|
formulaPickerValue: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleConfirm(value = '') {
|
||||||
|
this.props?.onChange?.(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
async handleOnClick(
|
||||||
|
e: React.MouseEvent,
|
||||||
|
onClick: (e: React.MouseEvent) => void
|
||||||
|
) {
|
||||||
|
const fieldsArr = await this.buildFieldsData();
|
||||||
|
this.setState({
|
||||||
|
fields: fieldsArr
|
||||||
|
});
|
||||||
|
|
||||||
|
return onClick?.(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {name, className, size} = this.props;
|
||||||
|
const {formulaPickerValue, fields} = this.state;
|
||||||
|
return (
|
||||||
|
<div className={cx('ae-ExpressionFormulaControl', className)}>
|
||||||
|
<PickerContainer
|
||||||
|
title="条件设置"
|
||||||
|
bodyRender={({
|
||||||
|
value,
|
||||||
|
onChange
|
||||||
|
}: {
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
value: any;
|
||||||
|
}) => {
|
||||||
|
const condition = renderAmis(
|
||||||
|
{
|
||||||
|
type: 'condition-builder',
|
||||||
|
label: false,
|
||||||
|
name,
|
||||||
|
fields: fields
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
onChange
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return condition;
|
||||||
|
}}
|
||||||
|
value={formulaPickerValue}
|
||||||
|
onConfirm={this.handleConfirm}
|
||||||
|
size={size ?? 'lg'}
|
||||||
|
>
|
||||||
|
{({onClick}: {onClick: (e: React.MouseEvent) => any}) => (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="btn-set-expression"
|
||||||
|
onClick={(e: any) => this.handleOnClick(e, onClick)}
|
||||||
|
>
|
||||||
|
点击编写条件
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</PickerContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FormItem({
|
||||||
|
type: 'ae-conditionFormulaControl'
|
||||||
|
})
|
||||||
|
export class ConditionFormulaControlRenderer extends ConditionFormulaControl {}
|
@ -30,6 +30,7 @@ export interface StatusControlProps extends FormControlProps {
|
|||||||
type StatusFormData = {
|
type StatusFormData = {
|
||||||
statusType: number;
|
statusType: number;
|
||||||
expression: string;
|
expression: string;
|
||||||
|
condition: object;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface StatusControlState {
|
interface StatusControlState {
|
||||||
@ -64,7 +65,11 @@ export class StatusControl extends React.Component<
|
|||||||
|
|
||||||
const formData: StatusFormData = {
|
const formData: StatusFormData = {
|
||||||
statusType: 1,
|
statusType: 1,
|
||||||
expression: ''
|
expression: '',
|
||||||
|
condition: {
|
||||||
|
conjunction: 'and',
|
||||||
|
children: []
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ctx = data;
|
let ctx = data;
|
||||||
@ -73,14 +78,29 @@ export class StatusControl extends React.Component<
|
|||||||
ctx = noBulkChangeData;
|
ctx = noBulkChangeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx[expressionName] || ctx[expressionName] === '') {
|
if (
|
||||||
|
typeof ctx[expressionName] === 'string' &&
|
||||||
|
(ctx[expressionName] || ctx[expressionName] === '')
|
||||||
|
) {
|
||||||
formData.statusType = 2;
|
formData.statusType = 2;
|
||||||
formData.expression = ctx[expressionName];
|
formData.expression = ctx[expressionName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof ctx[expressionName] === 'object' &&
|
||||||
|
ctx[expressionName] &&
|
||||||
|
ctx[expressionName].conjunction
|
||||||
|
) {
|
||||||
|
formData.statusType = 3;
|
||||||
|
formData.condition = ctx[expressionName];
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
checked:
|
checked:
|
||||||
ctx[name] == trueValue ||
|
ctx[name] == trueValue ||
|
||||||
typeof ctx[expressionName] === 'string' ||
|
typeof ctx[expressionName] === 'string' ||
|
||||||
|
Object.prototype.toString.call(ctx[expressionName]) ===
|
||||||
|
'[object Object]' ||
|
||||||
(!!defaultTrue &&
|
(!!defaultTrue &&
|
||||||
ctx[name] == undefined &&
|
ctx[name] == undefined &&
|
||||||
ctx[expressionName] == undefined),
|
ctx[expressionName] == undefined),
|
||||||
@ -98,7 +118,7 @@ export class StatusControl extends React.Component<
|
|||||||
@autobind
|
@autobind
|
||||||
handleSwitch(value: boolean) {
|
handleSwitch(value: boolean) {
|
||||||
const {trueValue, falseValue} = this.props;
|
const {trueValue, falseValue} = this.props;
|
||||||
const {expression, statusType = 1} = this.state.formData || {};
|
const {condition, expression, statusType = 1} = this.state.formData || {};
|
||||||
this.setState({checked: value == trueValue ? true : false}, () => {
|
this.setState({checked: value == trueValue ? true : false}, () => {
|
||||||
const {onBulkChange, noBulkChange, onDataChange, expressionName, name} =
|
const {onBulkChange, noBulkChange, onDataChange, expressionName, name} =
|
||||||
this.props;
|
this.props;
|
||||||
@ -115,6 +135,9 @@ export class StatusControl extends React.Component<
|
|||||||
case 2:
|
case 2:
|
||||||
newData[expressionName] = expression;
|
newData[expressionName] = expression;
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
newData[expressionName] = condition;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
!noBulkChange && onBulkChange && onBulkChange(newData);
|
!noBulkChange && onBulkChange && onBulkChange(newData);
|
||||||
@ -140,6 +163,9 @@ export class StatusControl extends React.Component<
|
|||||||
case 2:
|
case 2:
|
||||||
data[expressionName] = values.expression;
|
data[expressionName] = values.expression;
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
data[expressionName] = values.condition;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
!noBulkChange && onBulkChange && onBulkChange(data);
|
!noBulkChange && onBulkChange && onBulkChange(data);
|
||||||
onDataChange && onDataChange(data);
|
onDataChange && onDataChange(data);
|
||||||
@ -225,6 +251,11 @@ export class StatusControl extends React.Component<
|
|||||||
name: 'expression',
|
name: 'expression',
|
||||||
placeholder: `请输入${label}条件`,
|
placeholder: `请输入${label}条件`,
|
||||||
visibleOn: 'this.statusType === 2'
|
visibleOn: 'this.statusType === 2'
|
||||||
|
}),
|
||||||
|
getSchemaTpl('conditionFormulaControl', {
|
||||||
|
label: '条件设置',
|
||||||
|
name: 'condition',
|
||||||
|
visibleOn: 'this.statusType === 3'
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
89
packages/amis-editor/src/renderer/ValidateApiControl.tsx
Normal file
89
packages/amis-editor/src/renderer/ValidateApiControl.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* @file 表单项校验配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FormItem} from 'amis';
|
||||||
|
|
||||||
|
import {getSchemaTpl, tipedLabel} from 'amis-editor-core';
|
||||||
|
|
||||||
|
import type {FormControlProps} from 'amis-core';
|
||||||
|
|
||||||
|
export interface ValidationApiControlProps extends FormControlProps {}
|
||||||
|
|
||||||
|
export default class ValidationApiControl extends React.Component<ValidationApiControlProps> {
|
||||||
|
constructor(props: ValidationApiControlProps) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderValidateApiControl() {
|
||||||
|
const {onBulkChange, render} = this.props;
|
||||||
|
return (
|
||||||
|
<div className="ae-ValidationControl-item">
|
||||||
|
{render('validate-api-control', {
|
||||||
|
type: 'form',
|
||||||
|
wrapWithPanel: false,
|
||||||
|
className: 'w-full mb-2',
|
||||||
|
bodyClassName: 'p-none',
|
||||||
|
wrapperComponent: 'div',
|
||||||
|
mode: 'horizontal',
|
||||||
|
data: {
|
||||||
|
switchStatus: this.props.data.validateApi !== undefined
|
||||||
|
},
|
||||||
|
preventEnterSubmit: true,
|
||||||
|
submitOnChange: true,
|
||||||
|
onSubmit: ({switchStatus, validateApi}: any) => {
|
||||||
|
onBulkChange &&
|
||||||
|
onBulkChange({
|
||||||
|
validateApi: !switchStatus ? undefined : validateApi
|
||||||
|
});
|
||||||
|
},
|
||||||
|
body: [
|
||||||
|
getSchemaTpl('switch', {
|
||||||
|
label: tipedLabel(
|
||||||
|
'接口校验',
|
||||||
|
`配置校验接口,对表单项进行远程校验,配置方式与普通接口一致<br />
|
||||||
|
1. 接口返回 <span class="ae-ValidationControl-label-code">{status: 0}</span> 表示校验通过<br />
|
||||||
|
2. 接口返回 <span class="ae-ValidationControl-label-code">{status: 422}</span> 表示校验不通过<br />
|
||||||
|
3. 若校验失败时需要显示错误提示信息,还需返回 errors 字段,示例<br />
|
||||||
|
<span class="ae-ValidationControl-label-code">{status: 422, errors: '错误提示消息'}</span>
|
||||||
|
`
|
||||||
|
),
|
||||||
|
name: 'switchStatus'
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
type: 'container',
|
||||||
|
className: 'ae-ExtendMore ae-ValidationControl-item-input',
|
||||||
|
bodyClassName: 'w-full',
|
||||||
|
visibleOn: 'this.switchStatus',
|
||||||
|
data: {
|
||||||
|
// 放在form中则包含的表达式会被求值
|
||||||
|
validateApi: this.props.data.validateApi
|
||||||
|
},
|
||||||
|
body: [
|
||||||
|
getSchemaTpl('apiControl', {
|
||||||
|
name: 'validateApi',
|
||||||
|
renderLabel: true,
|
||||||
|
label: '',
|
||||||
|
mode: 'normal',
|
||||||
|
className: 'w-full'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <>{this.renderValidateApiControl()}</>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FormItem({
|
||||||
|
type: 'ae-validationApiControl',
|
||||||
|
renderLabel: false,
|
||||||
|
strictMode: false
|
||||||
|
})
|
||||||
|
export class ValidationApiControlRenderer extends ValidationApiControl {}
|
@ -6,9 +6,15 @@ import React, {ReactNode} from 'react';
|
|||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import remove from 'lodash/remove';
|
import remove from 'lodash/remove';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import {FormItem} from 'amis';
|
import {ConditionBuilderFields, FormItem} from 'amis';
|
||||||
|
|
||||||
import {autobind, getSchemaTpl, tipedLabel} from 'amis-editor-core';
|
import {
|
||||||
|
autobind,
|
||||||
|
getSchemaTpl,
|
||||||
|
getVariables,
|
||||||
|
isObjectShallowModified,
|
||||||
|
tipedLabel
|
||||||
|
} from 'amis-editor-core';
|
||||||
import ValidationItem, {ValidatorData} from './ValidationItem';
|
import ValidationItem, {ValidatorData} from './ValidationItem';
|
||||||
|
|
||||||
import type {FormControlProps} from 'amis-core';
|
import type {FormControlProps} from 'amis-core';
|
||||||
@ -34,31 +40,85 @@ interface ValidationControlState {
|
|||||||
defaultValidators: Record<string, Validator>;
|
defaultValidators: Record<string, Validator>;
|
||||||
builtInValidators: Record<string, Validator>;
|
builtInValidators: Record<string, Validator>;
|
||||||
};
|
};
|
||||||
|
fields: ConditionBuilderFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ValidationControl extends React.Component<
|
export default class ValidationControl extends React.Component<
|
||||||
ValidationControlProps,
|
ValidationControlProps,
|
||||||
ValidationControlState
|
ValidationControlState
|
||||||
> {
|
> {
|
||||||
|
cache?: any;
|
||||||
|
|
||||||
constructor(props: ValidationControlProps) {
|
constructor(props: ValidationControlProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
avaliableValids: this.getAvaliableValids(props)
|
avaliableValids: this.getAvaliableValids(props),
|
||||||
|
fields: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
const fieldsArr = await this.buildFieldsData();
|
||||||
|
this.setState({
|
||||||
|
fields: fieldsArr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: ValidationControlProps) {
|
componentWillReceiveProps(nextProps: ValidationControlProps) {
|
||||||
if (this.props.data.type !== nextProps.data.type) {
|
if (
|
||||||
|
this.props.data.type !== nextProps.data.type ||
|
||||||
|
this.cache?.required !== nextProps.data.required ||
|
||||||
|
isObjectShallowModified(
|
||||||
|
this.cache?.validations,
|
||||||
|
nextProps.data.validations
|
||||||
|
) ||
|
||||||
|
isObjectShallowModified(
|
||||||
|
this.cache?.validationErrors,
|
||||||
|
nextProps.data.validationErrors
|
||||||
|
)
|
||||||
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
avaliableValids: this.getAvaliableValids(nextProps)
|
avaliableValids: this.getAvaliableValids(nextProps)
|
||||||
});
|
});
|
||||||
const validators = this.transformValid(this.props.data);
|
// const validators = this.transformValid(this.props.data);
|
||||||
this.updateValidation(validators);
|
// this.updateValidation(validators);
|
||||||
}
|
}
|
||||||
// todo 删除不允许配置的值
|
// todo 删除不允许配置的值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
async buildFieldsData() {
|
||||||
|
const variablesArr = await getVariables(this);
|
||||||
|
// 自身字段
|
||||||
|
const selfName = this.props.data.name;
|
||||||
|
const vars =
|
||||||
|
variablesArr?.filter((item: any) => item?.label === '组件上下文')?.[0]
|
||||||
|
?.children?.[0]?.children || [];
|
||||||
|
|
||||||
|
const arr: ConditionBuilderFields = vars
|
||||||
|
.map((item: any) => {
|
||||||
|
if (item && item.value) {
|
||||||
|
let obj: any = {
|
||||||
|
label: item.label,
|
||||||
|
value: item.value
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selfName === item.value) {
|
||||||
|
obj = {
|
||||||
|
...obj,
|
||||||
|
label: item.label + '(self)',
|
||||||
|
disabled: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
?.filter((item: any) => item);
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
getAvaliableValids(props: ValidationControlProps) {
|
getAvaliableValids(props: ValidationControlProps) {
|
||||||
let {data, tag} = props;
|
let {data, tag} = props;
|
||||||
tag = typeof tag === 'string' ? tag : tag(data);
|
tag = typeof tag === 'string' ? tag : tag(data);
|
||||||
@ -96,6 +156,7 @@ export default class ValidationControl extends React.Component<
|
|||||||
const {onBulkChange} = this.props;
|
const {onBulkChange} = this.props;
|
||||||
|
|
||||||
if (!validators.length) {
|
if (!validators.length) {
|
||||||
|
this.cache = undefined;
|
||||||
onBulkChange &&
|
onBulkChange &&
|
||||||
onBulkChange({
|
onBulkChange({
|
||||||
required: undefined,
|
required: undefined,
|
||||||
@ -121,14 +182,15 @@ export default class ValidationControl extends React.Component<
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onBulkChange &&
|
this.cache = {
|
||||||
onBulkChange({
|
|
||||||
required,
|
required,
|
||||||
validations: Object.keys(validations).length ? validations : undefined,
|
validations: Object.keys(validations).length ? validations : undefined,
|
||||||
validationErrors: Object.keys(validationErrors).length
|
validationErrors: Object.keys(validationErrors).length
|
||||||
? validationErrors
|
? validationErrors
|
||||||
: undefined
|
: undefined
|
||||||
});
|
};
|
||||||
|
|
||||||
|
onBulkChange && onBulkChange({...this.cache});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -254,17 +316,18 @@ export default class ValidationControl extends React.Component<
|
|||||||
renderValidaton() {
|
renderValidaton() {
|
||||||
const classPrefix = this.props?.env?.theme?.classPrefix;
|
const classPrefix = this.props?.env?.theme?.classPrefix;
|
||||||
let {
|
let {
|
||||||
avaliableValids: {defaultValidators, moreValidators, builtInValidators}
|
avaliableValids: {defaultValidators, moreValidators, builtInValidators},
|
||||||
|
fields
|
||||||
} = this.state;
|
} = this.state;
|
||||||
let validators = this.transformValid(this.props.data);
|
let validators = this.transformValid(this.props.data);
|
||||||
const rules: ReactNode[] = [];
|
const rules: ReactNode[] = [];
|
||||||
validators = validators.concat();
|
validators = validators.concat();
|
||||||
|
|
||||||
// 优先渲染默认的顺序
|
// 优先渲染默认的顺序
|
||||||
Object.keys(defaultValidators).forEach((validName: string) => {
|
Object.keys(defaultValidators).forEach((validName: string) => {
|
||||||
const data = remove(validators, v => v.name === validName);
|
const data = remove(validators, v => v.name === validName);
|
||||||
rules.push(
|
rules.push(
|
||||||
<ValidationItem
|
<ValidationItem
|
||||||
|
fields={fields}
|
||||||
key={validName}
|
key={validName}
|
||||||
validator={defaultValidators[validName]}
|
validator={defaultValidators[validName]}
|
||||||
data={data.length ? data[0] : {name: validName}}
|
data={data.length ? data[0] : {name: validName}}
|
||||||
@ -281,11 +344,12 @@ export default class ValidationControl extends React.Component<
|
|||||||
const data = remove(validators, v => v.name === validName);
|
const data = remove(validators, v => v.name === validName);
|
||||||
rules.push(
|
rules.push(
|
||||||
<ValidationItem
|
<ValidationItem
|
||||||
|
fields={fields}
|
||||||
key={validName}
|
key={validName}
|
||||||
validator={builtInValidators[validName]}
|
validator={builtInValidators[validName]}
|
||||||
data={
|
data={
|
||||||
data.length
|
data.length
|
||||||
? data[0]
|
? {...data[0], isBuiltIn: true}
|
||||||
: {name: validName, value: true, isBuiltIn: true}
|
: {name: validName, value: true, isBuiltIn: true}
|
||||||
}
|
}
|
||||||
classPrefix={classPrefix}
|
classPrefix={classPrefix}
|
||||||
@ -307,6 +371,7 @@ export default class ValidationControl extends React.Component<
|
|||||||
}
|
}
|
||||||
rules.push(
|
rules.push(
|
||||||
<ValidationItem
|
<ValidationItem
|
||||||
|
fields={fields}
|
||||||
key={valid.name}
|
key={valid.name}
|
||||||
data={valid}
|
data={valid}
|
||||||
classPrefix={classPrefix}
|
classPrefix={classPrefix}
|
||||||
@ -323,67 +388,6 @@ export default class ValidationControl extends React.Component<
|
|||||||
return (
|
return (
|
||||||
<div className="ae-ValidationControl-rules" key="rules">
|
<div className="ae-ValidationControl-rules" key="rules">
|
||||||
{rules}
|
{rules}
|
||||||
{this.renderValidateApiControl()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderValidateApiControl() {
|
|
||||||
const {onBulkChange, render} = this.props;
|
|
||||||
return (
|
|
||||||
<div className="ae-ValidationControl-item">
|
|
||||||
{render('validate-api-control', {
|
|
||||||
type: 'form',
|
|
||||||
wrapWithPanel: false,
|
|
||||||
className: 'w-full mb-2',
|
|
||||||
bodyClassName: 'p-none',
|
|
||||||
wrapperComponent: 'div',
|
|
||||||
mode: 'horizontal',
|
|
||||||
data: {
|
|
||||||
switchStatus: this.props.data.validateApi !== undefined
|
|
||||||
},
|
|
||||||
preventEnterSubmit: true,
|
|
||||||
submitOnChange: true,
|
|
||||||
onSubmit: ({switchStatus, validateApi}: any) => {
|
|
||||||
onBulkChange &&
|
|
||||||
onBulkChange({
|
|
||||||
validateApi: !switchStatus ? undefined : validateApi
|
|
||||||
});
|
|
||||||
},
|
|
||||||
body: [
|
|
||||||
getSchemaTpl('switch', {
|
|
||||||
label: tipedLabel(
|
|
||||||
'接口校验',
|
|
||||||
`配置校验接口,对表单项进行远程校验,配置方式与普通接口一致<br />
|
|
||||||
1. 接口返回 <span class="ae-ValidationControl-label-code">{status: 0}</span> 表示校验通过<br />
|
|
||||||
2. 接口返回 <span class="ae-ValidationControl-label-code">{status: 422}</span> 表示校验不通过<br />
|
|
||||||
3. 若校验失败时需要显示错误提示信息,还需返回 errors 字段,示例<br />
|
|
||||||
<span class="ae-ValidationControl-label-code">{status: 422, errors: '错误提示消息'}</span>
|
|
||||||
`
|
|
||||||
),
|
|
||||||
name: 'switchStatus'
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
type: 'container',
|
|
||||||
className: 'ae-ExtendMore ae-ValidationControl-item-input',
|
|
||||||
bodyClassName: 'w-full',
|
|
||||||
visibleOn: 'this.switchStatus',
|
|
||||||
data: {
|
|
||||||
// 放在form中则包含的表达式会被求值
|
|
||||||
validateApi: this.props.data.validateApi
|
|
||||||
},
|
|
||||||
body: [
|
|
||||||
getSchemaTpl('apiControl', {
|
|
||||||
name: 'validateApi',
|
|
||||||
renderLabel: true,
|
|
||||||
label: '',
|
|
||||||
mode: 'normal',
|
|
||||||
className: 'w-full'
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import {render, Button, Switch} from 'amis';
|
|||||||
import {autobind, getI18nEnabled} from 'amis-editor-core';
|
import {autobind, getI18nEnabled} from 'amis-editor-core';
|
||||||
import {Validator} from '../validator';
|
import {Validator} from '../validator';
|
||||||
import {tipedLabel} from 'amis-editor-core';
|
import {tipedLabel} from 'amis-editor-core';
|
||||||
import type {SchemaCollection} from 'amis';
|
import type {ConditionBuilderFields, SchemaCollection} from 'amis';
|
||||||
|
|
||||||
export type ValidatorData = {
|
export type ValidatorData = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -36,6 +36,8 @@ export interface ValidationItemProps {
|
|||||||
|
|
||||||
validator: Validator;
|
validator: Validator;
|
||||||
|
|
||||||
|
fields?: ConditionBuilderFields;
|
||||||
|
|
||||||
onEdit?: (data: ValidatorData) => void;
|
onEdit?: (data: ValidatorData) => void;
|
||||||
onDelete?: (name: string) => void;
|
onDelete?: (name: string) => void;
|
||||||
onSwitch?: (checked: boolean, data?: ValidatorData) => void;
|
onSwitch?: (checked: boolean, data?: ValidatorData) => void;
|
||||||
@ -134,6 +136,7 @@ export default class ValidationItem extends React.Component<
|
|||||||
|
|
||||||
renderInputControl() {
|
renderInputControl() {
|
||||||
const {value, message, checked} = this.state;
|
const {value, message, checked} = this.state;
|
||||||
|
const {fields} = this.props;
|
||||||
const i18nEnabled = getI18nEnabled();
|
const i18nEnabled = getI18nEnabled();
|
||||||
let control: any = [];
|
let control: any = [];
|
||||||
|
|
||||||
@ -186,6 +189,10 @@ export default class ValidationItem extends React.Component<
|
|||||||
data: {value, message}
|
data: {value, message}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
data: {
|
||||||
|
...this.props.data,
|
||||||
|
fields
|
||||||
|
},
|
||||||
onSubmit: this.handleEdit
|
onSubmit: this.handleEdit
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
@ -144,6 +144,13 @@ setSchemaTpl('expressionFormulaControl', (schema: object = {}) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setSchemaTpl('conditionFormulaControl', (schema: object = {}) => {
|
||||||
|
return {
|
||||||
|
type: 'ae-conditionFormulaControl',
|
||||||
|
...schema
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
setSchemaTpl('textareaFormulaControl', (schema: object = {}) => {
|
setSchemaTpl('textareaFormulaControl', (schema: object = {}) => {
|
||||||
return {
|
return {
|
||||||
type: 'ae-textareaFormulaControl',
|
type: 'ae-textareaFormulaControl',
|
||||||
|
@ -558,6 +558,9 @@ setSchemaTpl(
|
|||||||
{
|
{
|
||||||
type: 'ae-validationControl',
|
type: 'ae-validationControl',
|
||||||
mode: 'normal',
|
mode: 'normal',
|
||||||
|
style: {
|
||||||
|
marginBottom: '6px'
|
||||||
|
},
|
||||||
...config
|
...config
|
||||||
// pipeIn: (value: any, data: any) => {
|
// pipeIn: (value: any, data: any) => {
|
||||||
// // return reduce(value, (arr: any, item) => {
|
// // return reduce(value, (arr: any, item) => {
|
||||||
@ -586,6 +589,7 @@ setSchemaTpl(
|
|||||||
// // }, []);
|
// // }, []);
|
||||||
// },
|
// },
|
||||||
},
|
},
|
||||||
|
getSchemaTpl('validationApiControl'),
|
||||||
getSchemaTpl('validateOnChange')
|
getSchemaTpl('validateOnChange')
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -593,6 +597,11 @@ setSchemaTpl(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setSchemaTpl('validationApiControl', {
|
||||||
|
type: 'ae-validationApiControl',
|
||||||
|
label: false
|
||||||
|
});
|
||||||
|
|
||||||
setSchemaTpl('validationControl', (value: Array<ValidationOptions> = []) => ({
|
setSchemaTpl('validationControl', (value: Array<ValidationOptions> = []) => ({
|
||||||
type: 'ae-validationControl',
|
type: 'ae-validationControl',
|
||||||
label: '校验规则',
|
label: '校验规则',
|
||||||
|
@ -65,6 +65,10 @@ export const registerValidator = (...config: Array<Validator>) => {
|
|||||||
Validators.push(...config);
|
Validators.push(...config);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeAllValidator = () => {
|
||||||
|
Validators.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
export const getValidatorsByTag = (tag: ValidatorTag) => {
|
export const getValidatorsByTag = (tag: ValidatorTag) => {
|
||||||
const defaultValidators: Record<string, Validator> = {};
|
const defaultValidators: Record<string, Validator> = {};
|
||||||
const moreValidators: Record<string, Validator> = {};
|
const moreValidators: Record<string, Validator> = {};
|
||||||
@ -117,7 +121,9 @@ export enum ValidatorTag {
|
|||||||
File = '8',
|
File = '8',
|
||||||
Date = '9',
|
Date = '9',
|
||||||
Code = '10',
|
Code = '10',
|
||||||
Tree = '11'
|
Tree = '11',
|
||||||
|
Phone = '12',
|
||||||
|
Tel = '13'
|
||||||
}
|
}
|
||||||
|
|
||||||
registerValidator(
|
registerValidator(
|
||||||
@ -133,7 +139,8 @@ registerValidator(
|
|||||||
[ValidatorTag.Email]: ValidTagMatchType.isDefault,
|
[ValidatorTag.Email]: ValidTagMatchType.isDefault,
|
||||||
[ValidatorTag.Password]: ValidTagMatchType.isDefault,
|
[ValidatorTag.Password]: ValidTagMatchType.isDefault,
|
||||||
[ValidatorTag.URL]: ValidTagMatchType.isDefault,
|
[ValidatorTag.URL]: ValidTagMatchType.isDefault,
|
||||||
[ValidatorTag.Tree]: ValidTagMatchType.isDefault
|
[ValidatorTag.Tree]: ValidTagMatchType.isDefault,
|
||||||
|
[ValidatorTag.Phone]: ValidTagMatchType.isDefault
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -345,7 +352,8 @@ registerValidator(
|
|||||||
group: ValidationGroup.Pattern,
|
group: ValidationGroup.Pattern,
|
||||||
message: '请输入合法的手机号码',
|
message: '请输入合法的手机号码',
|
||||||
tag: {
|
tag: {
|
||||||
[ValidatorTag.Text]: ValidTagMatchType.isMore
|
[ValidatorTag.Text]: ValidTagMatchType.isMore,
|
||||||
|
[ValidatorTag.Phone]: ValidTagMatchType.isBuiltIn
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -354,7 +362,8 @@ registerValidator(
|
|||||||
group: ValidationGroup.Pattern,
|
group: ValidationGroup.Pattern,
|
||||||
message: '请输入合法的电话号码',
|
message: '请输入合法的电话号码',
|
||||||
tag: {
|
tag: {
|
||||||
[ValidatorTag.Text]: ValidTagMatchType.isMore
|
[ValidatorTag.Text]: ValidTagMatchType.isMore,
|
||||||
|
[ValidatorTag.Tel]: ValidTagMatchType.isBuiltIn
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -408,6 +408,53 @@
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.#{$ns}Form-flex {
|
||||||
|
display: flex;
|
||||||
|
// flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$ns}Form-flex-col {
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
padding: calc(var(--Form-item-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$ns}Form-flexInner {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
> .#{$ns}Form-label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
padding-top: var(--Form-label-paddingTop);
|
||||||
|
margin-right: var(--Form-row-gutterWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .#{$ns}Form-control {
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$ns}Form-flexInner--label-top {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.#{$ns}Form-flexInner--label-left {
|
||||||
|
> .#{$ns}Form-label {
|
||||||
|
text-align: left;
|
||||||
|
width: px2rem(76px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.#{$ns}Form-flexInner--label-right {
|
||||||
|
> .#{$ns}Form-label {
|
||||||
|
text-align: right;
|
||||||
|
width: px2rem(76px);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}Form--debug {
|
.#{$ns}Form--debug {
|
||||||
@ -852,6 +899,42 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.#{$ns}Form-flexInner {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> .#{$ns}Form-label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
padding-top: var(--Form-label-paddingTop);
|
||||||
|
padding-right: var(--Form-row-gutterWidth);
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .#{$ns}Form-control {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.#{$ns}Form-flexInner--label-top {
|
||||||
|
flex-direction: column;
|
||||||
|
> .#{$ns}Form-label {
|
||||||
|
margin-bottom: px2rem(10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.#{$ns}Form-flexInner--label-left {
|
||||||
|
> .#{$ns}Form-label {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.#{$ns}Form-flexInner--label-right {
|
||||||
|
> .#{$ns}Form-label {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}Autofill-popOver {
|
.#{$ns}Autofill-popOver {
|
||||||
|
@ -366,7 +366,7 @@
|
|||||||
border-top-left-radius: 0 !important;
|
border-top-left-radius: 0 !important;
|
||||||
border-bottom-left-radius: 0 !important;
|
border-bottom-left-radius: 0 !important;
|
||||||
background-color: var(--inputNumber-base-unit-bg-color);
|
background-color: var(--inputNumber-base-unit-bg-color);
|
||||||
min-width: var(--inputNumber-base-default-unit-width) !important;
|
min-width: var(--inputNumber-base-default-unit-width);
|
||||||
padding: var(--inputNumber-base-default-unit-paddingTop)
|
padding: var(--inputNumber-base-default-unit-paddingTop)
|
||||||
var(--inputNumber-base-default-unit-paddingRight)
|
var(--inputNumber-base-default-unit-paddingRight)
|
||||||
var(--inputNumber-base-default-unit-paddingBottom)
|
var(--inputNumber-base-default-unit-paddingBottom)
|
||||||
|
@ -2,13 +2,7 @@ import React from 'react';
|
|||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import pickBy from 'lodash/pickBy';
|
import pickBy from 'lodash/pickBy';
|
||||||
import omitBy from 'lodash/omitBy';
|
import omitBy from 'lodash/omitBy';
|
||||||
import {
|
import {Renderer, RendererProps, filterTarget, mapTree} from 'amis-core';
|
||||||
Renderer,
|
|
||||||
RendererProps,
|
|
||||||
evalExpressionWithConditionBuilder,
|
|
||||||
filterTarget,
|
|
||||||
mapTree
|
|
||||||
} from 'amis-core';
|
|
||||||
import {SchemaNode, Schema, ActionObject, PlainObject} from 'amis-core';
|
import {SchemaNode, Schema, ActionObject, PlainObject} from 'amis-core';
|
||||||
import {CRUDStore, ICRUDStore, getMatchedEventTargets} from 'amis-core';
|
import {CRUDStore, ICRUDStore, getMatchedEventTargets} from 'amis-core';
|
||||||
import {
|
import {
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
RendererProps,
|
RendererProps,
|
||||||
ScopedContext,
|
ScopedContext,
|
||||||
buildStyle,
|
buildStyle,
|
||||||
evalExpressionWithConditionBuilder,
|
|
||||||
getMatchedEventTargets,
|
getMatchedEventTargets,
|
||||||
getPropValue
|
getPropValue
|
||||||
} from 'amis-core';
|
} from 'amis-core';
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
resolveEventData,
|
resolveEventData,
|
||||||
ApiObject,
|
ApiObject,
|
||||||
FormHorizontal,
|
FormHorizontal,
|
||||||
evalExpressionWithConditionBuilder,
|
evalExpressionWithConditionBuilderAsync,
|
||||||
IFormStore,
|
IFormStore,
|
||||||
getVariable,
|
getVariable,
|
||||||
IFormItemStore,
|
IFormItemStore,
|
||||||
@ -2033,7 +2033,7 @@ export class ComboControlRenderer extends ComboControl {
|
|||||||
} else if (condition !== undefined) {
|
} else if (condition !== undefined) {
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
const isUpdate = await evalExpressionWithConditionBuilder(
|
const isUpdate = await evalExpressionWithConditionBuilderAsync(
|
||||||
condition,
|
condition,
|
||||||
item
|
item
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
getRendererByName,
|
getRendererByName,
|
||||||
resolveEventData,
|
resolveEventData,
|
||||||
ListenerAction,
|
ListenerAction,
|
||||||
evalExpressionWithConditionBuilder,
|
evalExpressionWithConditionBuilderAsync,
|
||||||
mapTree,
|
mapTree,
|
||||||
isObject,
|
isObject,
|
||||||
eachTree,
|
eachTree,
|
||||||
@ -1949,7 +1949,7 @@ export class TableControlRenderer extends FormTable {
|
|||||||
const promises: Array<() => Promise<any>> = [];
|
const promises: Array<() => Promise<any>> = [];
|
||||||
everyTree(items, (item, index, paths, indexes) => {
|
everyTree(items, (item, index, paths, indexes) => {
|
||||||
promises.unshift(async () => {
|
promises.unshift(async () => {
|
||||||
const isUpdate = await evalExpressionWithConditionBuilder(
|
const isUpdate = await evalExpressionWithConditionBuilderAsync(
|
||||||
condition,
|
condition,
|
||||||
item
|
item
|
||||||
);
|
);
|
||||||
@ -2085,7 +2085,7 @@ export class TableControlRenderer extends FormTable {
|
|||||||
const promises: Array<() => Promise<any>> = [];
|
const promises: Array<() => Promise<any>> = [];
|
||||||
everyTree(items, (item, index, paths, indexes) => {
|
everyTree(items, (item, index, paths, indexes) => {
|
||||||
promises.unshift(async () => {
|
promises.unshift(async () => {
|
||||||
const result = await evalExpressionWithConditionBuilder(
|
const result = await evalExpressionWithConditionBuilderAsync(
|
||||||
args?.condition,
|
args?.condition,
|
||||||
item
|
item
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,6 @@ import Sortable from 'sortablejs';
|
|||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
import {
|
import {
|
||||||
ScopedContext,
|
ScopedContext,
|
||||||
evalExpressionWithConditionBuilder,
|
|
||||||
filterClassNameObject,
|
filterClassNameObject,
|
||||||
getMatchedEventTargets,
|
getMatchedEventTargets,
|
||||||
getPropValue
|
getPropValue
|
||||||
|
@ -16,7 +16,6 @@ import {
|
|||||||
SchemaExpression,
|
SchemaExpression,
|
||||||
position,
|
position,
|
||||||
animation,
|
animation,
|
||||||
evalExpressionWithConditionBuilder,
|
|
||||||
isEffectiveApi,
|
isEffectiveApi,
|
||||||
Renderer,
|
Renderer,
|
||||||
RendererProps,
|
RendererProps,
|
||||||
|
@ -30,7 +30,7 @@ import {
|
|||||||
isArrayChildrenModified,
|
isArrayChildrenModified,
|
||||||
filterTarget,
|
filterTarget,
|
||||||
changedEffect,
|
changedEffect,
|
||||||
evalExpressionWithConditionBuilder,
|
evalExpressionWithConditionBuilderAsync,
|
||||||
normalizeApi,
|
normalizeApi,
|
||||||
getPropValue
|
getPropValue
|
||||||
} from 'amis-core';
|
} from 'amis-core';
|
||||||
@ -2182,7 +2182,7 @@ export class TableRenderer extends Table2 {
|
|||||||
let items = [...store.data.rows];
|
let items = [...store.data.rows];
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
const isUpdate = await evalExpressionWithConditionBuilder(
|
const isUpdate = await evalExpressionWithConditionBuilderAsync(
|
||||||
condition,
|
condition,
|
||||||
item
|
item
|
||||||
);
|
);
|
||||||
|
@ -31,7 +31,7 @@ import {
|
|||||||
|
|
||||||
import {ActionSchema} from './Action';
|
import {ActionSchema} from './Action';
|
||||||
|
|
||||||
import {tokenize, evalExpressionWithConditionBuilder} from 'amis-core';
|
import {tokenize, evalExpressionWithConditionBuilderAsync} from 'amis-core';
|
||||||
import {StepSchema} from './Steps';
|
import {StepSchema} from './Steps';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import omit from 'lodash/omit';
|
import omit from 'lodash/omit';
|
||||||
@ -413,7 +413,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
|||||||
const stepsLength = steps.length;
|
const stepsLength = steps.length;
|
||||||
// 这里有个bug,如果把第一个step隐藏,表单就不会渲染
|
// 这里有个bug,如果把第一个step隐藏,表单就不会渲染
|
||||||
for (let i = 0; i < stepsLength; i++) {
|
for (let i = 0; i < stepsLength; i++) {
|
||||||
const hiddenFlag = await evalExpressionWithConditionBuilder(
|
const hiddenFlag = await evalExpressionWithConditionBuilderAsync(
|
||||||
steps[i].hiddenOn,
|
steps[i].hiddenOn,
|
||||||
values
|
values
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user