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 {
|
||||
resolveCondition,
|
||||
resolveConditionAsync,
|
||||
guid,
|
||||
registerConditionComputer,
|
||||
setConditionComputeErrorHandler
|
||||
@ -283,13 +283,13 @@ const conditions7 = {
|
||||
};
|
||||
|
||||
test(`condition`, async () => {
|
||||
expect(await resolveCondition(conditions1, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions2, data)).toBe(true);
|
||||
expect(await resolveCondition(conditions3, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions4, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions5, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions6, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions7, data)).toBe(false);
|
||||
expect(await resolveConditionAsync(conditions1, data)).toBe(false);
|
||||
expect(await resolveConditionAsync(conditions2, data)).toBe(true);
|
||||
expect(await resolveConditionAsync(conditions3, data)).toBe(false);
|
||||
expect(await resolveConditionAsync(conditions4, data)).toBe(false);
|
||||
expect(await resolveConditionAsync(conditions5, data)).toBe(false);
|
||||
expect(await resolveConditionAsync(conditions6, data)).toBe(false);
|
||||
expect(await resolveConditionAsync(conditions7, data)).toBe(false);
|
||||
});
|
||||
|
||||
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 () => {
|
||||
@ -466,9 +466,9 @@ test(`condition tree`, async () => {
|
||||
]
|
||||
};
|
||||
|
||||
expect(await resolveCondition(conditions8, data)).toBe(false);
|
||||
expect(await resolveCondition(conditions9, data)).toBe(true);
|
||||
expect(await resolveCondition(conditions10, data)).toBe(true);
|
||||
expect(await resolveConditionAsync(conditions8, data)).toBe(false);
|
||||
expect(await resolveConditionAsync(conditions9, data)).toBe(true);
|
||||
expect(await resolveConditionAsync(conditions10, data)).toBe(true);
|
||||
});
|
||||
|
||||
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 () => {
|
||||
@ -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 {createObject} from '../utils/helper';
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
||||
import {evalExpressionWithConditionBuilderAsync} from '../utils/tpl';
|
||||
import {dataMapping} from '../utils/tpl-builtin';
|
||||
import {IBreakAction} from './BreakAction';
|
||||
import {IContinueAction} from './ContinueAction';
|
||||
@ -247,7 +247,7 @@ export const runAction = async (
|
||||
let isStop = false;
|
||||
|
||||
if (expression) {
|
||||
isStop = !(await evalExpressionWithConditionBuilder(
|
||||
isStop = !(await evalExpressionWithConditionBuilderAsync(
|
||||
expression,
|
||||
mergeData,
|
||||
true
|
||||
@ -261,7 +261,7 @@ export const runAction = async (
|
||||
// 支持表达式 >=1.10.0
|
||||
let preventDefault = false;
|
||||
if (action.preventDefault) {
|
||||
preventDefault = await evalExpressionWithConditionBuilder(
|
||||
preventDefault = await evalExpressionWithConditionBuilderAsync(
|
||||
action.preventDefault,
|
||||
mergeData,
|
||||
false
|
||||
@ -362,7 +362,7 @@ export const runAction = async (
|
||||
|
||||
let stopPropagation = false;
|
||||
if (action.stopPropagation) {
|
||||
stopPropagation = await evalExpressionWithConditionBuilder(
|
||||
stopPropagation = await evalExpressionWithConditionBuilderAsync(
|
||||
action.stopPropagation,
|
||||
mergeData,
|
||||
false
|
||||
|
@ -3,7 +3,7 @@ import {normalizeApi, normalizeApiResponseData} from '../utils/api';
|
||||
import {ServerError} from '../utils/errors';
|
||||
import {createObject, isEmpty} from '../utils/helper';
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
||||
import {evalExpressionWithConditionBuilderAsync} from '../utils/tpl';
|
||||
import {
|
||||
RendererAction,
|
||||
ListenerAction,
|
||||
@ -61,7 +61,7 @@ export class AjaxAction implements RendererAction {
|
||||
|
||||
if (api.sendOn !== undefined) {
|
||||
// 发送请求前,判断是否需要发送
|
||||
const sendOn = await evalExpressionWithConditionBuilder(
|
||||
const sendOn = await evalExpressionWithConditionBuilderAsync(
|
||||
api.sendOn,
|
||||
action.data ?? {},
|
||||
false
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {RendererEvent} from '../utils/renderer-event';
|
||||
import {evalExpressionWithConditionBuilder} from '../utils/tpl';
|
||||
import {evalExpressionWithConditionBuilderAsync} from '../utils/tpl';
|
||||
import {
|
||||
RendererAction,
|
||||
ListenerContext,
|
||||
@ -27,7 +27,7 @@ export class SwitchAction implements RendererAction {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isPass = await evalExpressionWithConditionBuilder(
|
||||
const isPass = await evalExpressionWithConditionBuilderAsync(
|
||||
branch.expression,
|
||||
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> = {}
|
||||
): React.ReactNode {
|
||||
children = children || [];
|
||||
const {classnames: cx} = this.props;
|
||||
|
||||
if (!Array.isArray(children)) {
|
||||
children = [children];
|
||||
@ -1765,8 +1766,6 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {classnames: cx} = this.props;
|
||||
|
||||
return (
|
||||
<div className={cx('Form-row')}>
|
||||
{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) =>
|
||||
this.renderChild(control, key, otherProps, region)
|
||||
);
|
||||
@ -1841,7 +1897,10 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
formSubmited: form.submited,
|
||||
formMode: mode,
|
||||
formHorizontal: horizontal,
|
||||
formLabelAlign: labelAlign !== 'left' ? 'right' : labelAlign,
|
||||
formLabelAlign:
|
||||
!labelAlign || !['left', 'right', 'top'].includes(labelAlign)
|
||||
? 'right'
|
||||
: labelAlign,
|
||||
formLabelWidth: labelWidth,
|
||||
controlWidth,
|
||||
/**
|
||||
|
@ -48,7 +48,7 @@ import CustomStyle from '../components/CustomStyle';
|
||||
import classNames from 'classnames';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
|
||||
export type LabelAlign = 'right' | 'left';
|
||||
export type LabelAlign = 'right' | 'left' | 'top' | 'inherit';
|
||||
|
||||
export interface FormBaseControl extends BaseSchemaWithoutType {
|
||||
/**
|
||||
@ -461,6 +461,8 @@ export interface FormBaseControl extends BaseSchemaWithoutType {
|
||||
* 初始化时是否把其他字段同步到表单内部。
|
||||
*/
|
||||
initAutoFill?: boolean | 'fillIfNotSet';
|
||||
|
||||
row?: number; // flex模式下指定所在的行数
|
||||
}
|
||||
|
||||
export interface FormItemBasicConfig extends Partial<RendererConfig> {
|
||||
@ -1699,9 +1701,8 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
themeCss,
|
||||
id
|
||||
} = props;
|
||||
const labelWidth = props.labelWidth || props.formLabelWidth;
|
||||
description = description || desc;
|
||||
|
||||
const labelWidth = props.labelWidth || props.formLabelWidth;
|
||||
return (
|
||||
<div
|
||||
data-role="form-item"
|
||||
@ -1785,6 +1786,154 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
</ul>
|
||||
) : 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
|
||||
? render('description', description, {
|
||||
className: cx(
|
||||
@ -1940,7 +2089,12 @@ export const detectProps = [
|
||||
'displayMode',
|
||||
'revealPassword',
|
||||
'loading',
|
||||
'themeCss'
|
||||
'themeCss',
|
||||
'formLabelAlign',
|
||||
'formLabelWidth',
|
||||
'formHorizontal',
|
||||
'labelAlign',
|
||||
'colSize'
|
||||
];
|
||||
|
||||
export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||
@ -2060,7 +2214,10 @@ export function asFormItem(config: Omit<FormItemConfig, 'component'>) {
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
const controlSize = size || defaultSize;
|
||||
const controlSize =
|
||||
size && ['xs', 'sm', 'md', 'lg', 'full'].includes(size)
|
||||
? size
|
||||
: defaultSize;
|
||||
|
||||
//@ts-ignore
|
||||
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 {injectPropsToObject, mapObject} from './helper';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
@ -62,7 +66,8 @@ export function getExprProperties(
|
||||
|
||||
if (
|
||||
value &&
|
||||
typeof value === 'string' &&
|
||||
(typeof value === 'string' ||
|
||||
Object.prototype.toString.call(value) === '[object Object]') &&
|
||||
parts?.[1] &&
|
||||
(type === 'On' || type === 'Expr')
|
||||
) {
|
||||
@ -81,7 +86,9 @@ export function getExprProperties(
|
||||
}
|
||||
|
||||
if (type === 'On') {
|
||||
value = props?.[key] || evalExpression(value, ctx || data);
|
||||
value =
|
||||
props?.[key] ||
|
||||
evalExpressionWithConditionBuilder(value, ctx || data);
|
||||
} else {
|
||||
value = filter(value, ctx || data);
|
||||
}
|
||||
|
@ -2,7 +2,11 @@ import {Options} from '../types';
|
||||
import isPlainObject from 'lodash/isPlainObject';
|
||||
|
||||
export function normalizeOptions(
|
||||
options: string | {[propName: string]: string} | Array<string> | Options,
|
||||
options:
|
||||
| string
|
||||
| {[propName: string]: string}
|
||||
| Array<string | number>
|
||||
| Options,
|
||||
share: {
|
||||
values: Array<any>;
|
||||
options: Array<any>;
|
||||
@ -32,8 +36,9 @@ export function normalizeOptions(
|
||||
return option;
|
||||
});
|
||||
} else if (
|
||||
Array.isArray(options as Array<string>) &&
|
||||
typeof (options as Array<string>)[0] === 'string'
|
||||
Array.isArray(options as Array<string | number>) &&
|
||||
(typeof (options as Array<string | number>)[0] === 'string' ||
|
||||
typeof (options as Array<string | number>)[0] === 'number')
|
||||
) {
|
||||
return (options as Array<string>).map(item => {
|
||||
const idx = share.values.indexOf(item);
|
||||
|
@ -6,7 +6,7 @@ import {TreeItem, eachTree, getTree} from './helper';
|
||||
import {createObject, extendObject} from './object';
|
||||
import debounce from 'lodash/debounce';
|
||||
import {resolveVariableAndFilterForAsync} from './resolveVariableAndFilterForAsync';
|
||||
import {evalExpression, evalExpressionWithConditionBuilder} from './tpl';
|
||||
import {evalExpression, evalExpressionWithConditionBuilderAsync} from './tpl';
|
||||
|
||||
export interface debounceConfig {
|
||||
maxWait?: number;
|
||||
@ -379,7 +379,7 @@ export async function getMatchedEventTargets<T extends TreeItem>(
|
||||
eachTree(tree, item => {
|
||||
const data = item.storeType ? item.data : item;
|
||||
promies.push(async () => {
|
||||
const result = await evalExpressionWithConditionBuilder(
|
||||
const result = await evalExpressionWithConditionBuilderAsync(
|
||||
condition,
|
||||
createObject(ctx, data)
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import startsWith from 'lodash/startsWith';
|
||||
import {resolveVariableAndFilterForAsync} from './resolveVariableAndFilterForAsync';
|
||||
import moment from 'moment';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import {isPureVariable, resolveVariableAndFilter} from './tpl-builtin';
|
||||
|
||||
const conditionResolverMap: {
|
||||
[op: string]: (left: any, right: any, fieldType?: string) => boolean;
|
||||
@ -18,7 +19,7 @@ let conditionComputeErrorHandler: (
|
||||
defaultResult: boolean
|
||||
) => boolean | Promise<boolean>;
|
||||
|
||||
export async function resolveCondition(
|
||||
export async function resolveConditionAsync(
|
||||
conditions: any,
|
||||
data: any,
|
||||
defaultResult: boolean = true
|
||||
@ -33,7 +34,7 @@ export async function resolveCondition(
|
||||
}
|
||||
|
||||
try {
|
||||
return await computeConditions(
|
||||
return await computeConditionsAsync(
|
||||
conditions.children,
|
||||
conditions.conjunction,
|
||||
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[],
|
||||
conjunction: 'or' | 'and' = 'and',
|
||||
data: any
|
||||
@ -61,8 +91,8 @@ async function computeConditions(
|
||||
const item = conditions[index];
|
||||
const result =
|
||||
item.conjunction && Array.isArray(item.children) && item.children.length
|
||||
? await computeConditions(item.children, item.conjunction, data)
|
||||
: await computeCondition(item, index, data);
|
||||
? await computeConditionsAsync(item.children, item.conjunction, data)
|
||||
: await computeConditionAsync(item, index, data);
|
||||
|
||||
computeResult = !!result;
|
||||
|
||||
@ -76,7 +106,32 @@ async function computeConditions(
|
||||
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: {
|
||||
op: string;
|
||||
left: {
|
||||
@ -104,6 +159,32 @@ async function computeCondition(
|
||||
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) {
|
||||
if (left === undefined || right === undefined) {
|
||||
return DEFAULT_RESULT;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {register as registerBulitin, getFilters} from './tpl-builtin';
|
||||
import {register as registerLodash} from './tpl-lodash';
|
||||
import {parse, evaluate} from 'amis-formula';
|
||||
import {resolveCondition} from './resolveCondition';
|
||||
import {resolveCondition, resolveConditionAsync} from './resolveCondition';
|
||||
import {memoParse} from './tokenize';
|
||||
|
||||
export interface Enginer {
|
||||
@ -133,14 +133,33 @@ export function evalExpression(expression: string, data?: object): boolean {
|
||||
* @param data 上下文
|
||||
* @returns
|
||||
*/
|
||||
export async function evalExpressionWithConditionBuilder(
|
||||
export async function evalExpressionWithConditionBuilderAsync(
|
||||
expression: any,
|
||||
data?: object,
|
||||
defaultResult?: boolean
|
||||
): Promise<boolean> {
|
||||
// 支持ConditionBuilder
|
||||
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);
|
||||
|
@ -4,12 +4,13 @@
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid $editor-border-color;
|
||||
z-index: 1000;
|
||||
padding: 0 16px;
|
||||
|
||||
.ae-Breadcrumb-content {
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: max-content;
|
||||
padding: 0 16px;
|
||||
|
||||
white-space: nowrap;
|
||||
height: 22px;
|
||||
|
@ -80,6 +80,7 @@
|
||||
min-height: 450px;
|
||||
min-width: 980px;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
// 覆盖amis默认top值,避免导致未垂直居中
|
||||
.ae-Editor-toolbar svg.icon {
|
||||
@ -1199,7 +1200,7 @@
|
||||
|
||||
[data-editor-id].ae-is-draging,
|
||||
.ae-is-draging {
|
||||
display: none !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
[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 {
|
||||
margin-bottom: 12px;
|
||||
|
||||
@ -1872,3 +1901,19 @@ div.ae-DragImage {
|
||||
div[class*='Form-group']:empty {
|
||||
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 maxScrollLeft = scrollElem.offsetWidth - scrollContainer.offsetWidth;
|
||||
const maxScrollLeft =
|
||||
scrollElem.offsetWidth - scrollContainer.offsetWidth + 32;
|
||||
|
||||
if (scrollLeft - 50 > -maxScrollLeft) {
|
||||
scrollElem.style.left = `${scrollLeft - 50}px`;
|
||||
|
@ -31,6 +31,10 @@ export interface EditorProps extends PluginEventListener {
|
||||
$schemaUrl?: string;
|
||||
schemas?: Array<any>;
|
||||
theme?: string;
|
||||
/** 工具栏模式 */
|
||||
toolbarMode?: 'default' | 'mini';
|
||||
/** 是否需要弹框 */
|
||||
noDialog?: boolean;
|
||||
/** 应用语言类型 */
|
||||
appLocale?: string;
|
||||
/** 是否开启多语言 */
|
||||
@ -166,6 +170,8 @@ export default class Editor extends Component<EditorProps> {
|
||||
{
|
||||
isMobile: props.isMobile,
|
||||
theme: props.theme,
|
||||
toolbarMode: props.toolbarMode || 'default',
|
||||
noDialog: props.noDialog,
|
||||
isSubEditor,
|
||||
amisDocHost: props.amisDocHost,
|
||||
superEditorData,
|
||||
@ -592,7 +598,13 @@ export default class Editor extends Component<EditorProps> {
|
||||
|
||||
<div className="ae-Main">
|
||||
{!preview && (
|
||||
<Breadcrumb store={this.store} manager={this.manager} />
|
||||
<div className="ae-Header">
|
||||
<Breadcrumb store={this.store} manager={this.manager} />
|
||||
<div
|
||||
id="aeHeaderRightContainer"
|
||||
className="ae-Header-Right-Container"
|
||||
></div>
|
||||
</div>
|
||||
)}
|
||||
<Preview
|
||||
{...previewProps}
|
||||
|
@ -238,7 +238,8 @@ export default observer(function ({
|
||||
)}
|
||||
data-hlbox-id={id}
|
||||
style={{
|
||||
display: node.w && node.h ? 'block' : 'none',
|
||||
display:
|
||||
node.w && node.h && !node.info.plugin.notHighlight ? 'block' : 'none',
|
||||
top: node.y,
|
||||
left: node.x,
|
||||
width: node.w,
|
||||
|
@ -334,7 +334,7 @@ export class OutlinePanel extends React.Component<PanelProps> {
|
||||
)}
|
||||
</div>
|
||||
</Tab>
|
||||
{store.isSubEditor ? null : (
|
||||
{store.isSubEditor || store.noDialog ? null : (
|
||||
<Tab
|
||||
className={'ae-outline-tabs-panel'}
|
||||
key={'dialog-outline'}
|
||||
|
@ -635,8 +635,12 @@ class SmartPreview extends React.Component<SmartPreviewProps> {
|
||||
store.outline,
|
||||
item => !item.isRegion && item.clickable
|
||||
);
|
||||
|
||||
first && isAlive(store) && store.setActiveId(first.id);
|
||||
if (first && isAlive(store)) {
|
||||
const region = first.childRegions.find(
|
||||
(i: any) => i.region
|
||||
)?.region;
|
||||
store.setActiveId(first.id, region);
|
||||
}
|
||||
}
|
||||
}, 350);
|
||||
} 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 {EditorStoreType} from '../store/editor';
|
||||
import {EditorNodeType} from '../store/node';
|
||||
import {autobind, reactionWithOldValue, unitFormula} from '../util';
|
||||
import {
|
||||
JSONGetById,
|
||||
autobind,
|
||||
reactionWithOldValue,
|
||||
unitFormula
|
||||
} from '../util';
|
||||
import {DefaultDNDMode} from './default';
|
||||
import {DNDModeInterface} from './interface';
|
||||
import {PositionHDNDMode} from './position-h';
|
||||
import {FlexDNDMode} from './flex';
|
||||
|
||||
const toastWarning = debounce(msg => {
|
||||
toast.warning(msg);
|
||||
@ -175,15 +181,23 @@ export class EditorDNDManager {
|
||||
return this.dndMode || null;
|
||||
}
|
||||
const mode = region.regionInfo?.dndMode;
|
||||
const regionNode = JSONGetById(this.store.schema, region.id);
|
||||
let Klass: new (
|
||||
dnd: EditorDNDManager,
|
||||
region: EditorNodeType
|
||||
region: EditorNodeType,
|
||||
config: any
|
||||
) => DNDModeInterface = DefaultDNDMode; // todo 根据配置自动实例化不同的。
|
||||
|
||||
if (mode === 'position-h') {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -475,9 +489,20 @@ export class EditorDNDManager {
|
||||
}
|
||||
|
||||
const beforeId = this.dndMode?.getDropBeforeId();
|
||||
const position = this.dndMode?.getDropPosition?.();
|
||||
|
||||
// 如果中断 drop 事件,则直接返回
|
||||
if (this.dndMode?.interruptionDrop?.()) {
|
||||
return;
|
||||
}
|
||||
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') {
|
||||
let schema = store.dragSchema;
|
||||
const dropId = store.dropId;
|
||||
@ -494,11 +519,20 @@ export class EditorDNDManager {
|
||||
}
|
||||
}
|
||||
|
||||
this.manager.addChild(dropId, dropRegion, schema, beforeId, subRenderer, {
|
||||
id: store.dragId,
|
||||
type: store.dragType,
|
||||
data: store.dragSchema
|
||||
});
|
||||
this.manager.addChild(
|
||||
dropId,
|
||||
dropRegion,
|
||||
schema,
|
||||
beforeId,
|
||||
subRenderer,
|
||||
{
|
||||
id: store.dragId,
|
||||
type: store.dragType,
|
||||
data: store.dragSchema,
|
||||
position: position
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,8 @@
|
||||
import {
|
||||
DeleteEventContext,
|
||||
InsertEventContext,
|
||||
MoveEventContext
|
||||
} from '../plugin';
|
||||
import {EditorNodeType} from '../store/node';
|
||||
import {EditorDNDManager} from './index';
|
||||
|
||||
@ -17,4 +22,8 @@ export interface DNDModeInterface {
|
||||
getDropBeforeId: () => string | undefined;
|
||||
|
||||
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,
|
||||
JSONPipeOut,
|
||||
scrollToActive,
|
||||
JSONPipeIn
|
||||
JSONPipeIn,
|
||||
JSONGetById
|
||||
} from './util';
|
||||
import {hackIn, makeSchemaFormRender, makeWrapper} from './component/factory';
|
||||
import {env} from './env';
|
||||
@ -961,10 +962,6 @@ export class EditorManager {
|
||||
// 当前节点是布局类容器节点
|
||||
regionNodeId = curActiveId;
|
||||
regionNodeRegion = 'items';
|
||||
} else if (node.schema.fields && node.schema.type === 'doc-entity') {
|
||||
// 当前节点是表单视图
|
||||
regionNodeId = curActiveId;
|
||||
regionNodeRegion = 'fields';
|
||||
} else if (node.schema.body) {
|
||||
// 当前节点是容器节点
|
||||
regionNodeId = curActiveId;
|
||||
@ -1451,12 +1448,13 @@ export class EditorManager {
|
||||
sourceId: node.id,
|
||||
direction: 'up',
|
||||
beforeId: node.prevSibling?.id,
|
||||
region: regionNode.region
|
||||
region: regionNode.region,
|
||||
regionNode: regionNode
|
||||
};
|
||||
|
||||
const event = this.trigger('before-move', context);
|
||||
if (!event.prevented) {
|
||||
store.moveUp(node.id);
|
||||
store.moveUp(context);
|
||||
// this.buildToolbars();
|
||||
this.trigger('after-move', context);
|
||||
this.trigger('after-update', context);
|
||||
@ -1482,12 +1480,13 @@ export class EditorManager {
|
||||
sourceId: node.id,
|
||||
direction: 'down',
|
||||
beforeId: node.nextSibling?.nextSibling?.id,
|
||||
region: regionNode.region
|
||||
region: regionNode.region,
|
||||
regionNode: regionNode
|
||||
};
|
||||
|
||||
const event = this.trigger('before-move', context);
|
||||
if (!event.prevented) {
|
||||
store.moveDown(node.id);
|
||||
store.moveDown(context);
|
||||
// this.buildToolbars();
|
||||
this.trigger('after-move', context);
|
||||
this.trigger('after-update', context);
|
||||
@ -1512,8 +1511,7 @@ export class EditorManager {
|
||||
if (!event.prevented) {
|
||||
Array.isArray(context.data) && context.data.length
|
||||
? this.store.delMulti(context.data)
|
||||
: this.store.del(id);
|
||||
|
||||
: this.store.del(context);
|
||||
this.trigger('after-delete', context);
|
||||
}
|
||||
}
|
||||
@ -1617,6 +1615,7 @@ export class EditorManager {
|
||||
id: string;
|
||||
type: string;
|
||||
data: any;
|
||||
position?: string;
|
||||
},
|
||||
reGenerateId?: boolean
|
||||
): any | null {
|
||||
@ -1665,7 +1664,8 @@ export class EditorManager {
|
||||
id: string,
|
||||
region: string,
|
||||
sourceId: string,
|
||||
beforeId?: string
|
||||
beforeId?: string,
|
||||
dragInfo?: any
|
||||
): boolean {
|
||||
const store = this.store;
|
||||
|
||||
@ -1673,7 +1673,8 @@ export class EditorManager {
|
||||
...this.buildEventContext(id),
|
||||
beforeId,
|
||||
region: region,
|
||||
sourceId
|
||||
sourceId,
|
||||
dragInfo
|
||||
};
|
||||
|
||||
const event = this.trigger('before-move', context);
|
||||
|
@ -114,7 +114,9 @@ export interface RegionConfig {
|
||||
| 'default'
|
||||
| 'position-h'
|
||||
| '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>;
|
||||
|
||||
/**
|
||||
* 选中不需要高亮
|
||||
*/
|
||||
notHighlight?: boolean;
|
||||
|
||||
/**
|
||||
* 哪些容器属性需要自动转成数组的。如果不配置默认就从 regions 里面读取。
|
||||
*/
|
||||
@ -534,6 +541,7 @@ export interface InsertEventContext extends BaseEventContext {
|
||||
id: string;
|
||||
type: string;
|
||||
data: any;
|
||||
position?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@ -820,6 +828,11 @@ export interface PluginInterface
|
||||
*/
|
||||
async?: AsyncLayerOptions;
|
||||
|
||||
/**
|
||||
* 拖拽模式
|
||||
*/
|
||||
dragMode?: string;
|
||||
|
||||
/**
|
||||
* 有数据域的容器,可以为子组件提供读取的字段绑定页面
|
||||
*/
|
||||
|
@ -122,7 +122,8 @@ export class BasicToolbarPlugin extends BasePlugin {
|
||||
|
||||
if (
|
||||
!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;
|
||||
|
||||
@ -206,36 +207,37 @@ export class BasicToolbarPlugin extends BasePlugin {
|
||||
onClick: this.manager.del.bind(this.manager, id)
|
||||
});
|
||||
}
|
||||
if (store.toolbarMode === 'default') {
|
||||
toolbars.push({
|
||||
id: 'more',
|
||||
iconSvg: 'more-btn',
|
||||
icon: 'fa fa-cog',
|
||||
tooltip: '更多',
|
||||
placement: 'bottom',
|
||||
order: 1000,
|
||||
onClick: e => {
|
||||
if (!e.defaultPrevented) {
|
||||
const info = (
|
||||
e.target as HTMLElement
|
||||
).parentElement!.getBoundingClientRect();
|
||||
|
||||
toolbars.push({
|
||||
id: 'more',
|
||||
iconSvg: 'more-btn',
|
||||
icon: 'fa fa-cog',
|
||||
tooltip: '更多',
|
||||
placement: 'bottom',
|
||||
order: 1000,
|
||||
onClick: e => {
|
||||
if (!e.defaultPrevented) {
|
||||
const info = (
|
||||
e.target as HTMLElement
|
||||
).parentElement!.getBoundingClientRect();
|
||||
// 150 是 contextMenu 的宽度
|
||||
// 默认右对齐
|
||||
let x = window.scrollX + info.left + info.width - 150;
|
||||
|
||||
// 150 是 contextMenu 的宽度
|
||||
// 默认右对齐
|
||||
let x = window.scrollX + info.left + info.width - 150;
|
||||
// 显示不全是改成左对齐
|
||||
if (x < 0) {
|
||||
x = window.scrollX + info.left;
|
||||
}
|
||||
|
||||
// 显示不全是改成左对齐
|
||||
if (x < 0) {
|
||||
x = window.scrollX + info.left;
|
||||
this.manager.openContextMenu(id, '', {
|
||||
x: x,
|
||||
y: window.scrollY + info.top + info.height + 8
|
||||
});
|
||||
}
|
||||
|
||||
this.manager.openContextMenu(id, '', {
|
||||
x: x,
|
||||
y: window.scrollY + info.top + info.height + 8
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (info.scaffoldForm?.canRebuild ?? info.plugin.scaffoldForm?.canRebuild) {
|
||||
toolbars.push({
|
||||
@ -263,13 +265,15 @@ export class BasicToolbarPlugin extends BasePlugin {
|
||||
|
||||
if (selections.length) {
|
||||
// 多选时的右键菜单
|
||||
menus.push({
|
||||
id: 'copy',
|
||||
label: '重复一份',
|
||||
icon: 'copy-icon',
|
||||
disabled: selections.some(item => !item.node.duplicatable),
|
||||
onSelect: () => manager.duplicate(selections.map(item => item.id))
|
||||
});
|
||||
if (store.toolbarMode === 'default') {
|
||||
menus.push({
|
||||
id: 'copy',
|
||||
label: '重复一份',
|
||||
icon: 'copy-icon',
|
||||
disabled: selections.some(item => !item.node.duplicatable),
|
||||
onSelect: () => manager.duplicate(selections.map(item => item.id))
|
||||
});
|
||||
}
|
||||
|
||||
menus.push({
|
||||
id: 'unselect',
|
||||
@ -320,6 +324,9 @@ export class BasicToolbarPlugin extends BasePlugin {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (store.toolbarMode === 'mini') {
|
||||
return;
|
||||
}
|
||||
menus.push({
|
||||
id: 'select',
|
||||
label: `选中${first.label}`,
|
||||
|
@ -32,14 +32,15 @@ export class DataDebugPlugin extends BasePlugin {
|
||||
// return;
|
||||
// }
|
||||
const store = comp.props.store;
|
||||
|
||||
toolbars.push({
|
||||
icon: 'fa fa-bug',
|
||||
order: -1000,
|
||||
placement: 'bottom',
|
||||
tooltip: '上下文数据',
|
||||
onClick: () => this.openDebugForm(comp.props.data)
|
||||
});
|
||||
if (store.toolbarMode === 'default') {
|
||||
toolbars.push({
|
||||
icon: 'fa fa-bug',
|
||||
order: -1000,
|
||||
placement: 'bottom',
|
||||
tooltip: '上下文数据',
|
||||
onClick: () => this.openDebugForm(comp.props.data)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
dataViewer = {
|
||||
|
@ -34,7 +34,9 @@ import {
|
||||
PanelItem,
|
||||
MoveEventContext,
|
||||
ScaffoldForm,
|
||||
PopOverForm
|
||||
PopOverForm,
|
||||
DeleteEventContext,
|
||||
BaseEventContext
|
||||
} from '../plugin';
|
||||
import {
|
||||
JSONDuplicate,
|
||||
@ -59,6 +61,7 @@ import {matchSorter} from 'match-sorter';
|
||||
import debounce from 'lodash/debounce';
|
||||
import type {DialogSchema} from '../../../amis/src/renderers/Dialog';
|
||||
import type {DrawerSchema} from '../../../amis/src/renderers/Drawer';
|
||||
import getLayoutInstance from '../layout';
|
||||
|
||||
export interface SchemaHistory {
|
||||
versionId: number;
|
||||
@ -152,6 +155,8 @@ export const MainStore = types
|
||||
label: 'Root'
|
||||
}),
|
||||
theme: 'cxd', // 主题,默认cxd主题
|
||||
toolbarMode: 'default', // 工具栏模式,默认default,mini模式没有更多、前后插入组件、上下文数据、重复一份、合成一行、右键功能
|
||||
noDialog: false, // 不需要弹框功能
|
||||
hoverId: '',
|
||||
hoverRegion: '',
|
||||
activeId: '',
|
||||
@ -1236,6 +1241,11 @@ export const MainStore = types
|
||||
// 显然有错误。
|
||||
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);
|
||||
|
||||
@ -1267,13 +1277,21 @@ export const MainStore = types
|
||||
arr.push(child);
|
||||
}
|
||||
|
||||
event.context.data = child;
|
||||
event.context.regionList = arr;
|
||||
afterInsert && (event.context = afterInsert(event.context, self));
|
||||
|
||||
this.traceableSetSchema(
|
||||
JSONUpdate(self.schema, id, {
|
||||
[region]: arr
|
||||
[region]: event.context.regionList
|
||||
})
|
||||
);
|
||||
|
||||
event.context.data = child;
|
||||
child?.$$id &&
|
||||
setTimeout(() => {
|
||||
this.setActiveId(child.$$id);
|
||||
}, 0);
|
||||
|
||||
return child;
|
||||
},
|
||||
|
||||
@ -1285,12 +1303,17 @@ export const MainStore = types
|
||||
if (context.sourceId === context.beforeId) {
|
||||
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);
|
||||
schema = JSONDelete(schema, context.sourceId, undefined, true);
|
||||
|
||||
const region = context.region;
|
||||
|
||||
const json = JSONGetById(schema, context.id);
|
||||
let origin = json[region];
|
||||
origin = Array.isArray(origin)
|
||||
@ -1300,11 +1323,10 @@ export const MainStore = types
|
||||
: [];
|
||||
|
||||
if (context.beforeId) {
|
||||
const idx = findIndex(
|
||||
let idx = findIndex(
|
||||
origin,
|
||||
(item: any) => item.$$id === context.beforeId
|
||||
);
|
||||
|
||||
if (!~idx) {
|
||||
throw new Error('位置错误,目标位置没有找到');
|
||||
}
|
||||
@ -1313,9 +1335,12 @@ export const MainStore = types
|
||||
origin.push(source);
|
||||
}
|
||||
|
||||
event.context.regionList = origin;
|
||||
afterMove && (event.context = afterMove(event.context, self));
|
||||
|
||||
this.traceableSetSchema(
|
||||
JSONUpdate(schema, context.id, {
|
||||
[region]: origin
|
||||
[region]: event.context.regionList
|
||||
})
|
||||
);
|
||||
},
|
||||
@ -1603,35 +1628,84 @@ export const MainStore = types
|
||||
}
|
||||
},
|
||||
|
||||
moveUp(id: string) {
|
||||
if (!id) {
|
||||
moveUp(context: BaseEventContext) {
|
||||
const {sourceId, regionNode, region, id} = context;
|
||||
if (!sourceId) {
|
||||
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) {
|
||||
if (!id) {
|
||||
moveDown(context: BaseEventContext) {
|
||||
const {sourceId, regionNode, region, id} = context;
|
||||
if (!sourceId) {
|
||||
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) {
|
||||
const host = self.getNodeById(id)?.host;
|
||||
this.setActiveId(host ? host.id : '');
|
||||
const node = self.getNodeById(id);
|
||||
this.setActiveId(node?.parentId || '', node?.parentRegion);
|
||||
} else if (self.activeId) {
|
||||
const active = JSONGetById(self.schema, id);
|
||||
|
||||
// 如果当前点选的是要删的节点里面的,则改成选中当前要删的上层
|
||||
if (JSONGetById(active, self.activeId)) {
|
||||
const host = self.getNodeById(id)?.host;
|
||||
this.setActiveId(host ? host.id : '');
|
||||
const node = self.getNodeById(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>) {
|
||||
|
@ -16,6 +16,7 @@ import isNumber from 'lodash/isNumber';
|
||||
import debounce from 'lodash/debounce';
|
||||
import merge from 'lodash/merge';
|
||||
import {EditorModalBody} from './store/editor';
|
||||
import {filter} from 'lodash';
|
||||
|
||||
const {
|
||||
guid,
|
||||
@ -1578,3 +1579,28 @@ export function mergeDefinitions(
|
||||
|
||||
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';
|
||||
import './tpl/index';
|
||||
export * from './plugin';
|
||||
export * from './validator';
|
||||
|
||||
import './renderer/OptionControl';
|
||||
import './renderer/ValueFormatControl';
|
||||
@ -16,11 +17,13 @@ import './renderer/TimelineItemControl';
|
||||
import './renderer/APIControl';
|
||||
import './renderer/APIAdaptorControl';
|
||||
import './renderer/ValidationControl';
|
||||
import './renderer/ValidateApiControl';
|
||||
import './renderer/ValidationItem';
|
||||
import './renderer/SwitchMoreControl';
|
||||
import './renderer/StatusControl';
|
||||
import './renderer/FormulaControl';
|
||||
import './renderer/ExpressionFormulaControl';
|
||||
import './renderer/ConditionFormulaControl';
|
||||
import './renderer/textarea-formula/TextareaFormulaControl';
|
||||
import './renderer/TplFormulaControl';
|
||||
import './renderer/DateShortCutControl';
|
||||
|
@ -24,7 +24,8 @@ export class DividerPlugin extends BasePlugin {
|
||||
description = '用来展示一个分割线,可用来做视觉上的隔离。';
|
||||
docLink = '/amis/zh-CN/components/divider';
|
||||
scaffold = {
|
||||
type: 'divider'
|
||||
type: 'divider',
|
||||
$$dragMode: 'hv'
|
||||
};
|
||||
previewSchema: any = {
|
||||
type: 'divider',
|
||||
|
@ -20,7 +20,10 @@ import {
|
||||
ScaffoldForm,
|
||||
RegionConfig,
|
||||
registerEditorPlugin,
|
||||
JSONPipeOut
|
||||
JSONPipeOut,
|
||||
InsertEventContext,
|
||||
MoveEventContext,
|
||||
DeleteEventContext
|
||||
} from 'amis-editor-core';
|
||||
import {
|
||||
DSFeatureType,
|
||||
|
@ -268,6 +268,9 @@ export class ItemPlugin extends BasePlugin {
|
||||
{id, schema, region, selections}: ContextMenuEventContext,
|
||||
menus: Array<ContextMenuItem>
|
||||
) {
|
||||
if (this.manager.store.toolbarMode === 'mini') {
|
||||
return;
|
||||
}
|
||||
if (!selections.length || selections.length > 3) {
|
||||
// 单选或者超过3个选中态时直接返回
|
||||
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 = {
|
||||
statusType: number;
|
||||
expression: string;
|
||||
condition: object;
|
||||
};
|
||||
|
||||
interface StatusControlState {
|
||||
@ -64,7 +65,11 @@ export class StatusControl extends React.Component<
|
||||
|
||||
const formData: StatusFormData = {
|
||||
statusType: 1,
|
||||
expression: ''
|
||||
expression: '',
|
||||
condition: {
|
||||
conjunction: 'and',
|
||||
children: []
|
||||
}
|
||||
};
|
||||
|
||||
let ctx = data;
|
||||
@ -73,14 +78,29 @@ export class StatusControl extends React.Component<
|
||||
ctx = noBulkChangeData;
|
||||
}
|
||||
|
||||
if (ctx[expressionName] || ctx[expressionName] === '') {
|
||||
if (
|
||||
typeof ctx[expressionName] === 'string' &&
|
||||
(ctx[expressionName] || ctx[expressionName] === '')
|
||||
) {
|
||||
formData.statusType = 2;
|
||||
formData.expression = ctx[expressionName];
|
||||
}
|
||||
|
||||
if (
|
||||
typeof ctx[expressionName] === 'object' &&
|
||||
ctx[expressionName] &&
|
||||
ctx[expressionName].conjunction
|
||||
) {
|
||||
formData.statusType = 3;
|
||||
formData.condition = ctx[expressionName];
|
||||
}
|
||||
|
||||
return {
|
||||
checked:
|
||||
ctx[name] == trueValue ||
|
||||
typeof ctx[expressionName] === 'string' ||
|
||||
Object.prototype.toString.call(ctx[expressionName]) ===
|
||||
'[object Object]' ||
|
||||
(!!defaultTrue &&
|
||||
ctx[name] == undefined &&
|
||||
ctx[expressionName] == undefined),
|
||||
@ -98,7 +118,7 @@ export class StatusControl extends React.Component<
|
||||
@autobind
|
||||
handleSwitch(value: boolean) {
|
||||
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}, () => {
|
||||
const {onBulkChange, noBulkChange, onDataChange, expressionName, name} =
|
||||
this.props;
|
||||
@ -115,6 +135,9 @@ export class StatusControl extends React.Component<
|
||||
case 2:
|
||||
newData[expressionName] = expression;
|
||||
break;
|
||||
case 3:
|
||||
newData[expressionName] = condition;
|
||||
break;
|
||||
}
|
||||
}
|
||||
!noBulkChange && onBulkChange && onBulkChange(newData);
|
||||
@ -140,6 +163,9 @@ export class StatusControl extends React.Component<
|
||||
case 2:
|
||||
data[expressionName] = values.expression;
|
||||
break;
|
||||
case 3:
|
||||
data[expressionName] = values.condition;
|
||||
break;
|
||||
}
|
||||
!noBulkChange && onBulkChange && onBulkChange(data);
|
||||
onDataChange && onDataChange(data);
|
||||
@ -225,6 +251,11 @@ export class StatusControl extends React.Component<
|
||||
name: 'expression',
|
||||
placeholder: `请输入${label}条件`,
|
||||
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 remove from 'lodash/remove';
|
||||
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 type {FormControlProps} from 'amis-core';
|
||||
@ -34,31 +40,85 @@ interface ValidationControlState {
|
||||
defaultValidators: Record<string, Validator>;
|
||||
builtInValidators: Record<string, Validator>;
|
||||
};
|
||||
fields: ConditionBuilderFields;
|
||||
}
|
||||
|
||||
export default class ValidationControl extends React.Component<
|
||||
ValidationControlProps,
|
||||
ValidationControlState
|
||||
> {
|
||||
cache?: any;
|
||||
|
||||
constructor(props: ValidationControlProps) {
|
||||
super(props);
|
||||
|
||||
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) {
|
||||
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({
|
||||
avaliableValids: this.getAvaliableValids(nextProps)
|
||||
});
|
||||
const validators = this.transformValid(this.props.data);
|
||||
this.updateValidation(validators);
|
||||
// const validators = this.transformValid(this.props.data);
|
||||
// this.updateValidation(validators);
|
||||
}
|
||||
// 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) {
|
||||
let {data, tag} = props;
|
||||
tag = typeof tag === 'string' ? tag : tag(data);
|
||||
@ -96,6 +156,7 @@ export default class ValidationControl extends React.Component<
|
||||
const {onBulkChange} = this.props;
|
||||
|
||||
if (!validators.length) {
|
||||
this.cache = undefined;
|
||||
onBulkChange &&
|
||||
onBulkChange({
|
||||
required: undefined,
|
||||
@ -121,14 +182,15 @@ export default class ValidationControl extends React.Component<
|
||||
}
|
||||
});
|
||||
|
||||
onBulkChange &&
|
||||
onBulkChange({
|
||||
required,
|
||||
validations: Object.keys(validations).length ? validations : undefined,
|
||||
validationErrors: Object.keys(validationErrors).length
|
||||
? validationErrors
|
||||
: undefined
|
||||
});
|
||||
this.cache = {
|
||||
required,
|
||||
validations: Object.keys(validations).length ? validations : undefined,
|
||||
validationErrors: Object.keys(validationErrors).length
|
||||
? validationErrors
|
||||
: undefined
|
||||
};
|
||||
|
||||
onBulkChange && onBulkChange({...this.cache});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -254,17 +316,18 @@ export default class ValidationControl extends React.Component<
|
||||
renderValidaton() {
|
||||
const classPrefix = this.props?.env?.theme?.classPrefix;
|
||||
let {
|
||||
avaliableValids: {defaultValidators, moreValidators, builtInValidators}
|
||||
avaliableValids: {defaultValidators, moreValidators, builtInValidators},
|
||||
fields
|
||||
} = this.state;
|
||||
let validators = this.transformValid(this.props.data);
|
||||
const rules: ReactNode[] = [];
|
||||
validators = validators.concat();
|
||||
|
||||
// 优先渲染默认的顺序
|
||||
Object.keys(defaultValidators).forEach((validName: string) => {
|
||||
const data = remove(validators, v => v.name === validName);
|
||||
rules.push(
|
||||
<ValidationItem
|
||||
fields={fields}
|
||||
key={validName}
|
||||
validator={defaultValidators[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);
|
||||
rules.push(
|
||||
<ValidationItem
|
||||
fields={fields}
|
||||
key={validName}
|
||||
validator={builtInValidators[validName]}
|
||||
data={
|
||||
data.length
|
||||
? data[0]
|
||||
? {...data[0], isBuiltIn: true}
|
||||
: {name: validName, value: true, isBuiltIn: true}
|
||||
}
|
||||
classPrefix={classPrefix}
|
||||
@ -307,6 +371,7 @@ export default class ValidationControl extends React.Component<
|
||||
}
|
||||
rules.push(
|
||||
<ValidationItem
|
||||
fields={fields}
|
||||
key={valid.name}
|
||||
data={valid}
|
||||
classPrefix={classPrefix}
|
||||
@ -323,67 +388,6 @@ export default class ValidationControl extends React.Component<
|
||||
return (
|
||||
<div className="ae-ValidationControl-rules" key="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>
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import {render, Button, Switch} from 'amis';
|
||||
import {autobind, getI18nEnabled} from 'amis-editor-core';
|
||||
import {Validator} from '../validator';
|
||||
import {tipedLabel} from 'amis-editor-core';
|
||||
import type {SchemaCollection} from 'amis';
|
||||
import type {ConditionBuilderFields, SchemaCollection} from 'amis';
|
||||
|
||||
export type ValidatorData = {
|
||||
name: string;
|
||||
@ -36,6 +36,8 @@ export interface ValidationItemProps {
|
||||
|
||||
validator: Validator;
|
||||
|
||||
fields?: ConditionBuilderFields;
|
||||
|
||||
onEdit?: (data: ValidatorData) => void;
|
||||
onDelete?: (name: string) => void;
|
||||
onSwitch?: (checked: boolean, data?: ValidatorData) => void;
|
||||
@ -134,6 +136,7 @@ export default class ValidationItem extends React.Component<
|
||||
|
||||
renderInputControl() {
|
||||
const {value, message, checked} = this.state;
|
||||
const {fields} = this.props;
|
||||
const i18nEnabled = getI18nEnabled();
|
||||
let control: any = [];
|
||||
|
||||
@ -186,6 +189,10 @@ export default class ValidationItem extends React.Component<
|
||||
data: {value, message}
|
||||
},
|
||||
{
|
||||
data: {
|
||||
...this.props.data,
|
||||
fields
|
||||
},
|
||||
onSubmit: this.handleEdit
|
||||
}
|
||||
)}
|
||||
|
@ -144,6 +144,13 @@ setSchemaTpl('expressionFormulaControl', (schema: object = {}) => {
|
||||
};
|
||||
});
|
||||
|
||||
setSchemaTpl('conditionFormulaControl', (schema: object = {}) => {
|
||||
return {
|
||||
type: 'ae-conditionFormulaControl',
|
||||
...schema
|
||||
};
|
||||
});
|
||||
|
||||
setSchemaTpl('textareaFormulaControl', (schema: object = {}) => {
|
||||
return {
|
||||
type: 'ae-textareaFormulaControl',
|
||||
|
@ -558,6 +558,9 @@ setSchemaTpl(
|
||||
{
|
||||
type: 'ae-validationControl',
|
||||
mode: 'normal',
|
||||
style: {
|
||||
marginBottom: '6px'
|
||||
},
|
||||
...config
|
||||
// pipeIn: (value: any, data: any) => {
|
||||
// // return reduce(value, (arr: any, item) => {
|
||||
@ -586,6 +589,7 @@ setSchemaTpl(
|
||||
// // }, []);
|
||||
// },
|
||||
},
|
||||
getSchemaTpl('validationApiControl'),
|
||||
getSchemaTpl('validateOnChange')
|
||||
]
|
||||
};
|
||||
@ -593,6 +597,11 @@ setSchemaTpl(
|
||||
}
|
||||
);
|
||||
|
||||
setSchemaTpl('validationApiControl', {
|
||||
type: 'ae-validationApiControl',
|
||||
label: false
|
||||
});
|
||||
|
||||
setSchemaTpl('validationControl', (value: Array<ValidationOptions> = []) => ({
|
||||
type: 'ae-validationControl',
|
||||
label: '校验规则',
|
||||
|
@ -65,6 +65,10 @@ export const registerValidator = (...config: Array<Validator>) => {
|
||||
Validators.push(...config);
|
||||
};
|
||||
|
||||
export const removeAllValidator = () => {
|
||||
Validators.length = 0;
|
||||
};
|
||||
|
||||
export const getValidatorsByTag = (tag: ValidatorTag) => {
|
||||
const defaultValidators: Record<string, Validator> = {};
|
||||
const moreValidators: Record<string, Validator> = {};
|
||||
@ -117,7 +121,9 @@ export enum ValidatorTag {
|
||||
File = '8',
|
||||
Date = '9',
|
||||
Code = '10',
|
||||
Tree = '11'
|
||||
Tree = '11',
|
||||
Phone = '12',
|
||||
Tel = '13'
|
||||
}
|
||||
|
||||
registerValidator(
|
||||
@ -133,7 +139,8 @@ registerValidator(
|
||||
[ValidatorTag.Email]: ValidTagMatchType.isDefault,
|
||||
[ValidatorTag.Password]: 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,
|
||||
message: '请输入合法的手机号码',
|
||||
tag: {
|
||||
[ValidatorTag.Text]: ValidTagMatchType.isMore
|
||||
[ValidatorTag.Text]: ValidTagMatchType.isMore,
|
||||
[ValidatorTag.Phone]: ValidTagMatchType.isBuiltIn
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -354,7 +362,8 @@ registerValidator(
|
||||
group: ValidationGroup.Pattern,
|
||||
message: '请输入合法的电话号码',
|
||||
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 {
|
||||
@ -852,6 +899,42 @@
|
||||
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 {
|
||||
|
@ -366,7 +366,7 @@
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
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)
|
||||
var(--inputNumber-base-default-unit-paddingRight)
|
||||
var(--inputNumber-base-default-unit-paddingBottom)
|
||||
|
@ -2,13 +2,7 @@ import React from 'react';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import pickBy from 'lodash/pickBy';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
import {
|
||||
Renderer,
|
||||
RendererProps,
|
||||
evalExpressionWithConditionBuilder,
|
||||
filterTarget,
|
||||
mapTree
|
||||
} from 'amis-core';
|
||||
import {Renderer, RendererProps, filterTarget, mapTree} from 'amis-core';
|
||||
import {SchemaNode, Schema, ActionObject, PlainObject} from 'amis-core';
|
||||
import {CRUDStore, ICRUDStore, getMatchedEventTargets} from 'amis-core';
|
||||
import {
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
RendererProps,
|
||||
ScopedContext,
|
||||
buildStyle,
|
||||
evalExpressionWithConditionBuilder,
|
||||
getMatchedEventTargets,
|
||||
getPropValue
|
||||
} from 'amis-core';
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
resolveEventData,
|
||||
ApiObject,
|
||||
FormHorizontal,
|
||||
evalExpressionWithConditionBuilder,
|
||||
evalExpressionWithConditionBuilderAsync,
|
||||
IFormStore,
|
||||
getVariable,
|
||||
IFormItemStore,
|
||||
@ -2033,7 +2033,7 @@ export class ComboControlRenderer extends ComboControl {
|
||||
} else if (condition !== undefined) {
|
||||
for (let i = 0; i < len; i++) {
|
||||
const item = items[i];
|
||||
const isUpdate = await evalExpressionWithConditionBuilder(
|
||||
const isUpdate = await evalExpressionWithConditionBuilderAsync(
|
||||
condition,
|
||||
item
|
||||
);
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
getRendererByName,
|
||||
resolveEventData,
|
||||
ListenerAction,
|
||||
evalExpressionWithConditionBuilder,
|
||||
evalExpressionWithConditionBuilderAsync,
|
||||
mapTree,
|
||||
isObject,
|
||||
eachTree,
|
||||
@ -1949,7 +1949,7 @@ export class TableControlRenderer extends FormTable {
|
||||
const promises: Array<() => Promise<any>> = [];
|
||||
everyTree(items, (item, index, paths, indexes) => {
|
||||
promises.unshift(async () => {
|
||||
const isUpdate = await evalExpressionWithConditionBuilder(
|
||||
const isUpdate = await evalExpressionWithConditionBuilderAsync(
|
||||
condition,
|
||||
item
|
||||
);
|
||||
@ -2085,7 +2085,7 @@ export class TableControlRenderer extends FormTable {
|
||||
const promises: Array<() => Promise<any>> = [];
|
||||
everyTree(items, (item, index, paths, indexes) => {
|
||||
promises.unshift(async () => {
|
||||
const result = await evalExpressionWithConditionBuilder(
|
||||
const result = await evalExpressionWithConditionBuilderAsync(
|
||||
args?.condition,
|
||||
item
|
||||
);
|
||||
|
@ -4,7 +4,6 @@ import Sortable from 'sortablejs';
|
||||
import omit from 'lodash/omit';
|
||||
import {
|
||||
ScopedContext,
|
||||
evalExpressionWithConditionBuilder,
|
||||
filterClassNameObject,
|
||||
getMatchedEventTargets,
|
||||
getPropValue
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
SchemaExpression,
|
||||
position,
|
||||
animation,
|
||||
evalExpressionWithConditionBuilder,
|
||||
isEffectiveApi,
|
||||
Renderer,
|
||||
RendererProps,
|
||||
|
@ -30,7 +30,7 @@ import {
|
||||
isArrayChildrenModified,
|
||||
filterTarget,
|
||||
changedEffect,
|
||||
evalExpressionWithConditionBuilder,
|
||||
evalExpressionWithConditionBuilderAsync,
|
||||
normalizeApi,
|
||||
getPropValue
|
||||
} from 'amis-core';
|
||||
@ -2182,7 +2182,7 @@ export class TableRenderer extends Table2 {
|
||||
let items = [...store.data.rows];
|
||||
for (let i = 0; i < len; i++) {
|
||||
const item = items[i];
|
||||
const isUpdate = await evalExpressionWithConditionBuilder(
|
||||
const isUpdate = await evalExpressionWithConditionBuilderAsync(
|
||||
condition,
|
||||
item
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ import {
|
||||
|
||||
import {ActionSchema} from './Action';
|
||||
|
||||
import {tokenize, evalExpressionWithConditionBuilder} from 'amis-core';
|
||||
import {tokenize, evalExpressionWithConditionBuilderAsync} from 'amis-core';
|
||||
import {StepSchema} from './Steps';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import omit from 'lodash/omit';
|
||||
@ -413,7 +413,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
|
||||
const stepsLength = steps.length;
|
||||
// 这里有个bug,如果把第一个step隐藏,表单就不会渲染
|
||||
for (let i = 0; i < stepsLength; i++) {
|
||||
const hiddenFlag = await evalExpressionWithConditionBuilder(
|
||||
const hiddenFlag = await evalExpressionWithConditionBuilderAsync(
|
||||
steps[i].hiddenOn,
|
||||
values
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user