feat: 公式支持快捷变量支持

This commit is contained in:
yupeng12 2024-06-06 17:31:34 +08:00
parent 09e564e9e7
commit 0a436fb137
6 changed files with 138 additions and 78 deletions

View File

@ -4,7 +4,7 @@
import {hasIcon, mapObject, utils} from 'amis'; import {hasIcon, mapObject, utils} from 'amis';
import type {PlainObject, Schema, SchemaNode} from 'amis'; import type {PlainObject, Schema, SchemaNode} from 'amis';
import {getGlobalData} from 'amis-theme-editor-helper'; import {getGlobalData} from 'amis-theme-editor-helper';
import {isExpression, resolveVariableAndFilter} from 'amis-core'; import {filterTree, isExpression, resolveVariableAndFilter} from 'amis-core';
import type {VariableItem} from 'amis-ui'; import type {VariableItem} from 'amis-ui';
import {isObservable, reaction} from 'mobx'; import {isObservable, reaction} from 'mobx';
import DeepDiff, {Diff} from 'deep-diff'; import DeepDiff, {Diff} from 'deep-diff';
@ -1362,65 +1362,78 @@ export async function getVariables(that: any) {
return variablesArr; return variablesArr;
} }
export async function getQuickVariables( export async function getQuickVariables(that: any) {
that: any,
resolver?: (vars: any[]) => any[]
) {
const {node, manager} = that.props.formProps || that.props; const {node, manager} = that.props.formProps || that.props;
const selfName = that.props?.data?.name; const {quickVars, data} = that.props;
const selfName = data?.name;
await manager?.getContextSchemas(node); await manager?.getContextSchemas(node);
const options = await manager?.dataSchema?.getDataPropsAsOptions(); const options = await manager?.dataSchema?.getDataPropsAsOptions();
if (Array.isArray(options)) { if (Array.isArray(options)) {
const vars = options.find(item => item.label === '组件上下文'); const curOptions = options.find(item => item.label === '组件上下文');
if (vars?.children?.length) { return resolveQuickVariables(curOptions, quickVars, selfName);
// 获取当前层的变量
const current: any[] = vars.children[0].children || [];
const filterVars = current
.filter(item => item.value !== selfName && item.schemaType)
.map(item => {
// 子表过滤成员那层
if (item.type === 'array' && Array.isArray(item.children)) {
if (item.children.length === 1) {
const child = item.children[0];
if (child.type === 'object' && child.disabled) {
return {
...item,
children: child.children
};
}
}
}
return item;
});
return resolver ? resolver(filterVars) : filterVars;
}
} }
return []; return [];
} }
export function resolveQuickVariablesByType( export function resolveQuickVariables(
variables: VariableItem[], option: any,
quickVars?: VariableItem[] quickVars?: VariableItem[],
selfName?: string
) { ) {
if (!option?.children?.length) {
return [];
}
const finalVars = [];
const curOption = option.children[0];
const superOption = option.children[1];
const variables = (curOption.children || [])
.filter((item: any) => item.value !== selfName && item.schemaType)
.map((item: any) => {
// 子表过滤成员那层
if (item.type === 'array' && Array.isArray(item.children)) {
if (item.children.length === 1) {
const child = item.children[0];
if (child.type === 'object' && child.disabled) {
return {
...item,
children: child.children
};
}
}
}
return item;
});
if (superOption?.children?.length) {
const superVars = superOption?.children.filter(
(item: any) => item.schemaType && item.type !== 'array'
);
finalVars.push(...superVars);
finalVars.push({
label: curOption.label,
children: variables
});
} else {
finalVars.push(...variables);
}
if (quickVars?.length) { if (quickVars?.length) {
const vars: VariableItem[] = []; const vars: VariableItem[] = [];
vars.push({ vars.push({
label: '快捷变量', label: '快捷变量',
children: quickVars children: quickVars
}); });
if (variables.length) { if (finalVars.length) {
vars.push({ vars.push({
label: '表单变量', label: '表单变量',
children: variables children: finalVars
}); });
} }
return vars; return vars;
} }
return variables; return finalVars;
} }
/** /**

View File

@ -20,11 +20,7 @@ import FormulaPicker, {
import {JSONPipeOut, autobind, translateSchema} from 'amis-editor-core'; import {JSONPipeOut, autobind, translateSchema} from 'amis-editor-core';
import {EditorManager} from 'amis-editor-core'; import {EditorManager} from 'amis-editor-core';
import {reaction} from 'mobx'; import {reaction} from 'mobx';
import { import {getVariables, getQuickVariables} from 'amis-editor-core';
getVariables,
getQuickVariables,
resolveQuickVariablesByType
} from 'amis-editor-core';
import type {BaseEventContext} from 'amis-editor-core'; import type {BaseEventContext} from 'amis-editor-core';
import type {VariableItem, FuncGroup} from 'amis-ui'; import type {VariableItem, FuncGroup} from 'amis-ui';
@ -215,14 +211,11 @@ export default class FormulaControl extends React.Component<
} }
); );
const {valueType, quickVars} = this.props;
const variables = await getVariables(this); const variables = await getVariables(this);
const quickVariables = await getQuickVariables(this, items => const quickVariables = await getQuickVariables(this);
items.filter(item => item.schemaType === valueType)
);
this.setState({ this.setState({
variables, variables,
quickVariables: resolveQuickVariablesByType(quickVariables, quickVars) quickVariables: this.filterQuickVariablesByType(quickVariables)
}); });
} }
@ -236,6 +229,26 @@ export default class FormulaControl extends React.Component<
this.buttonTarget = ref; this.buttonTarget = ref;
} }
@autobind
filterQuickVariablesByType(variables: any[]) {
const rendererSchema = this.getRendererSchemaFromProps();
const filterVars = variables
.map(item => {
if (item.children) {
item.children = item.children.filter(
(i: any) => i.schemaType === rendererSchema.type
);
}
return item;
})
.filter(
item =>
item.schemaType === rendererSchema.type ||
(item.children && item.children?.length)
);
return filterVars;
}
/** /**
* ${xx} \${xx} \${xx} * ${xx} \${xx} \${xx}
* 备注: 手动编辑时 ${xx} * 备注: 手动编辑时 ${xx}
@ -664,6 +677,7 @@ export default class FormulaControl extends React.Component<
data={quickVariables} data={quickVariables}
onSelect={this.handleQuickVariableSelect} onSelect={this.handleQuickVariableSelect}
popOverContainer={popOverContainer} popOverContainer={popOverContainer}
simplifyMemberOprs
/> />
</ul> </ul>
); );

View File

@ -55,6 +55,11 @@ export interface TplFormulaControlProps extends FormControlProps {
* *
*/ */
quickVariables?: boolean; quickVariables?: boolean;
/**
*
*/
quickVars?: Array<VariableItem>;
} }
interface TplFormulaControlState { interface TplFormulaControlState {
@ -148,7 +153,10 @@ export class TplFormulaControl extends React.Component<
const variables = await getVariables(this); const variables = await getVariables(this);
const quickVariables = await getQuickVariables(this); const quickVariables = await getQuickVariables(this);
this.setState({variables, quickVariables}); this.setState({
variables,
quickVariables
});
} }
componentWillUnmount() { componentWillUnmount() {
@ -474,6 +482,7 @@ export class TplFormulaControl extends React.Component<
data={quickVariables} data={quickVariables}
onSelect={this.handleQuickVariableSelect} onSelect={this.handleQuickVariableSelect}
popOverContainer={popOverContainer} popOverContainer={popOverContainer}
simplifyMemberOprs
/> />
</ul> </ul>
); );

View File

@ -484,6 +484,7 @@ export class TextareaFormulaControl extends React.Component<
data={quickVariables} data={quickVariables}
onSelect={this.handleQuickVariableSelect} onSelect={this.handleQuickVariableSelect}
popOverContainer={popOverContainer} popOverContainer={popOverContainer}
simplifyMemberOprs
/> />
</ul> </ul>
); );

View File

@ -302,6 +302,7 @@
white-space: nowrap; white-space: nowrap;
& > label { & > label {
cursor: pointer;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -311,6 +312,10 @@
min-width: #{px2rem(50px)}; min-width: #{px2rem(50px)};
} }
& > .icon {
cursor: pointer;
}
.fa-ellipsis-h { .fa-ellipsis-h {
margin-right: #{px2rem(5px)}; margin-right: #{px2rem(5px)};
&:hover { &:hover {
@ -319,6 +324,7 @@
} }
&-tag { &-tag {
cursor: auto;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
padding: 0 #{px2rem(8px)}; padding: 0 #{px2rem(8px)};

View File

@ -5,6 +5,7 @@ import GroupedSelection from '../GroupedSelection';
import Tabs, {Tab} from '../Tabs'; import Tabs, {Tab} from '../Tabs';
import TreeSelection from '../TreeSelection'; import TreeSelection from '../TreeSelection';
import SearchBox from '../SearchBox'; import SearchBox from '../SearchBox';
import {Icon} from '../icons';
import type {VariableItem} from './CodeEditor'; import type {VariableItem} from './CodeEditor';
import type {ItemRenderStates} from '../Selection'; import type {ItemRenderStates} from '../Selection';
@ -42,29 +43,34 @@ const memberOpers = [
'即当前列表中所有符合该成员条件的记录总数需补充成员条件例如item.xxx === 1' '即当前列表中所有符合该成员条件的记录总数需补充成员条件例如item.xxx === 1'
}, },
{ {
label: '取该成员去重之后的总数', label: '去重计数:取该成员去重之后的总数',
value: 'COUNT(UNIQ(${arr}, item.${member}))', value: 'COUNT(UNIQ(${arr}, item.${member}))',
description: '即对该成员记录进行去重,并统计总数' description: '即对该成员记录进行去重,并统计总数',
simple: true
}, },
{ {
label: '取该成员的总和', label: '求和:求当前列的所有值之和',
value: 'SUM(ARRAYMAP(${arr}, item => item.${member}))', value: 'SUM(ARRAYMAP(${arr}, item => item.${member}))',
description: '即计算该成员记录的总和,需确认该成员记录均为数字类型' description: '即计算该成员记录的总和,需确认该成员记录均为数字类型',
simple: true
}, },
{ {
label: '取该成员的平均值', label: '平均值:求当前列的平均值',
value: 'AVG(ARRAYMAP(${arr}, item => item.${member}))', value: 'AVG(ARRAYMAP(${arr}, item => item.${member}))',
description: '即计算该成员记录的平均值,需确认该成员记录均为数字类型' description: '即计算该成员记录的平均值,需确认该成员记录均为数字类型',
simple: true
}, },
{ {
label: '取该成员的最大值', label: '最大值:取当前列的最大值',
value: 'MAX(ARRAYMAP(${arr}, item => item.${member}))', value: 'MAX(ARRAYMAP(${arr}, item => item.${member}))',
description: '即计算该成员记录中最大值,需确认该成员记录均为数字类型' description: '即计算该成员记录中最大值,需确认该成员记录均为数字类型',
simple: true
}, },
{ {
label: '取该成员的最小值', label: '最小值:取当前列的最小值',
value: 'MIN(ARRAYMAP(${arr}, item => item.${member}))', value: 'MIN(ARRAYMAP(${arr}, item => item.${member}))',
description: '即计算该成员记录中最小值,需确认该成员记录均为数字类型' description: '即计算该成员记录中最小值,需确认该成员记录均为数字类型',
simple: true
} }
]; ];
@ -80,6 +86,7 @@ export interface VariableListProps extends ThemeProps, SpinnerExtraProps {
onSelect?: (item: VariableItem) => void; onSelect?: (item: VariableItem) => void;
selfVariableName?: string; selfVariableName?: string;
expandTree?: boolean; expandTree?: boolean;
simplifyMemberOprs?: boolean;
popOverContainer?: () => HTMLElement; popOverContainer?: () => HTMLElement;
} }
@ -96,6 +103,7 @@ function VariableList(props: VariableListProps) {
placeholderRender, placeholderRender,
selfVariableName, selfVariableName,
expandTree, expandTree,
simplifyMemberOprs,
popOverContainer popOverContainer
} = props; } = props;
const [variables, setVariables] = React.useState<Array<VariableItem>>([]); const [variables, setVariables] = React.useState<Array<VariableItem>>([]);
@ -177,28 +185,30 @@ function VariableList(props: VariableListProps) {
} }
popOverRender={({onClose}) => ( popOverRender={({onClose}) => (
<ul className={cx(`${classPrefix}-item-oper`)}> <ul className={cx(`${classPrefix}-item-oper`)}>
{memberOpers.map((item, i) => { {memberOpers
return ( .filter(item => !simplifyMemberOprs || item.simple)
<TooltipWrapper .map((item, i) => {
key={i} return (
tooltip={item.description} <TooltipWrapper
tooltipTheme="dark"
>
<li
key={i} key={i}
onClick={() => tooltip={item.description}
handleMemberClick( tooltipTheme="dark"
{...item, isMember: true},
option,
onClose
)
}
> >
<span>{item.label}</span> <li
</li> key={i}
</TooltipWrapper> onClick={() =>
); handleMemberClick(
})} {...item, isMember: true},
option,
onClose
)
}
>
<span>{item.label}</span>
</li>
</TooltipWrapper>
);
})}
</ul> </ul>
)} )}
> >
@ -207,7 +217,14 @@ function VariableList(props: VariableListProps) {
tooltip={option.description ?? option.label} tooltip={option.description ?? option.label}
tooltipTheme="dark" tooltipTheme="dark"
> >
<label onClick={onClick}>{option.label}</label> <>
<label onClick={onClick}>{option.label}</label>
<Icon
onClick={onClick}
icon="ellipsis-v"
className="icon"
/>
</>
</TooltipWrapper> </TooltipWrapper>
)} )}
</PopOverContainer> </PopOverContainer>