-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: schemaSettingDefaultValue support expression #4294
Open
katherinehhh
wants to merge
9
commits into
main
Choose a base branch
from
T-4232
base: main
Could not load branches
Branch not found: {{ refName }}
Could not load tags
Nothing to show
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+389
−26
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
89a3382
feat: schemaSettingDefaultValue support expression
katherinehhh 39f64f6
Merge branch 'main' into T-4232
katherinehhh fa4945b
Merge branch 'main' into T-4232
katherinehhh 15ae2f0
fix: bug
katherinehhh 4b2ed06
Merge branch 'main' into T-4232
katherinehhh c08404b
test: skipunstable test
katherinehhh 6da7ed6
test: e2e test
katherinehhh 80c51b0
refactor: code improve
katherinehhh b59cbfb
refactor: code improve
katherinehhh File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
339 changes: 339 additions & 0 deletions
339
packages/core/client/src/schema-component/antd/form-item/VariableInput/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,339 @@ | ||
/** | ||
* This file is part of the NocoBase (R) project. | ||
* Copyright (c) 2020-2024 NocoBase Co., Ltd. | ||
* Authors: NocoBase Team. | ||
* | ||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. | ||
* For more information, please refer to: https://www.nocobase.com/agreement. | ||
*/ | ||
|
||
import { Form } from '@formily/core'; | ||
// @ts-ignore | ||
import { Schema } from '@formily/json-schema'; | ||
import _ from 'lodash'; | ||
import React, { useCallback } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { CollectionFieldOptions_deprecated } from '../../../../collection-manager'; | ||
import { Variable, useVariableScope } from '../../../../schema-component'; | ||
import { useValues } from '../../../../schema-component/antd/filter/useValues'; | ||
import { VariableOption, VariablesContextType } from '../../../../variables/types'; | ||
import { isVariable } from '../../../../variables/utils/isVariable'; | ||
import { useBlockCollection } from '../../../../schema-settings/VariableInput/hooks/useBlockCollection'; | ||
import { useContextAssociationFields } from '../../../../schema-settings/VariableInput/hooks/useContextAssociationFields'; | ||
import { useCurrentRecordVariable } from '../../../../schema-settings/VariableInput/hooks/useRecordVariable'; | ||
import { useCurrentUserVariable } from '../../../../schema-settings/VariableInput/hooks/useUserVariable'; | ||
import { useVariableOptions } from '../../../../schema-settings/VariableInput/hooks/useVariableOptions'; | ||
import { Option } from '../../../../schema-settings/VariableInput/type'; | ||
|
||
interface GetShouldChangeProps { | ||
collectionField: CollectionFieldOptions_deprecated; | ||
variables: VariablesContextType; | ||
localVariables: VariableOption | VariableOption[]; | ||
/** `useCollectionManager_deprecated` 返回的 */ | ||
getAllCollectionsInheritChain: (collectionName: string) => string[]; | ||
} | ||
|
||
interface RenderSchemaComponentProps { | ||
value: any; | ||
onChange: (value: any) => void; | ||
} | ||
|
||
type Props = { | ||
value: any; | ||
onChange: (value: any, optionPath?: any[]) => void; | ||
renderSchemaComponent: (props: RenderSchemaComponentProps) => any; | ||
schema?: any; | ||
/** 消费变量值的字段 */ | ||
targetFieldSchema?: Schema; | ||
children?: any; | ||
className?: string; | ||
style?: React.CSSProperties; | ||
collectionField: CollectionFieldOptions_deprecated; | ||
contextCollectionName?: string; | ||
/** | ||
* 根据 `onChange` 的第一个参数,判断是否需要触发 `onChange` | ||
* @param value `onChange` 的第一个参数 | ||
* @returns 返回为 `true` 时,才会触发 `onChange` | ||
*/ | ||
shouldChange?: (value: any, optionPath?: any[]) => Promise<boolean>; | ||
form?: Form; | ||
/** | ||
* 当前表单的记录,数据来自数据库 | ||
*/ | ||
record?: Record<string, any>; | ||
/** | ||
* 可以用该方法对内部的 scope 进行筛选和修改 | ||
* @param scope | ||
* @returns | ||
*/ | ||
returnScope?: (scope: Option[]) => any[]; | ||
}; | ||
|
||
/** | ||
* 注意:该组件存在以下问题: | ||
* - 在选中选项的时候该组件不能触发重渲染 | ||
* - 如果触发重渲染可能会导致无法展开子选项列表 | ||
* @param props | ||
* @returns | ||
*/ | ||
export const VariableInput = (props: Props) => { | ||
const { | ||
value, | ||
onChange, | ||
renderSchemaComponent: RenderSchemaComponent, | ||
style, | ||
schema, | ||
className, | ||
contextCollectionName, | ||
collectionField, | ||
shouldChange, | ||
form, | ||
record, | ||
returnScope = _.identity, | ||
targetFieldSchema, | ||
} = props; | ||
const { name: blockCollectionName } = useBlockCollection(); | ||
const scope = useVariableScope(); | ||
const { operator, schema: uiSchema = collectionField?.uiSchema } = useValues(); | ||
|
||
const variableOptions = useVariableOptions({ | ||
collectionField, | ||
form, | ||
record, | ||
operator, | ||
uiSchema, | ||
targetFieldSchema, | ||
}); | ||
const contextVariable = useContextAssociationFields({ schema, maxDepth: 2, contextCollectionName, collectionField }); | ||
const { compatOldVariables } = useCompatOldVariables({ | ||
collectionField, | ||
uiSchema, | ||
targetFieldSchema, | ||
blockCollectionName, | ||
}); | ||
|
||
if (contextCollectionName && variableOptions.every((item) => item.value !== contextVariable.value)) { | ||
variableOptions.push(contextVariable); | ||
} | ||
|
||
const handleChange = useCallback( | ||
(value: any, optionPath: any[]) => { | ||
if (!shouldChange) { | ||
return onChange(value); | ||
} | ||
// `shouldChange` 这个函数的运算量比较大,会导致展开变量列表时有明显的卡顿感,在这里加个延迟能有效解决这个问题 | ||
setTimeout(async () => { | ||
if (await shouldChange(value, optionPath)) { | ||
onChange(value); | ||
} | ||
}); | ||
}, | ||
[onChange, shouldChange], | ||
); | ||
return ( | ||
<Variable.TextArea | ||
className={className} | ||
value={value} | ||
onChange={handleChange} | ||
scope={returnScope( | ||
compatOldVariables(_.isEmpty(scope) ? variableOptions : scope, { | ||
value, | ||
}), | ||
)} | ||
style={style} | ||
// changeOnSelect | ||
> | ||
<RenderSchemaComponent value={value} onChange={onChange} /> | ||
</Variable.TextArea> | ||
); | ||
}; | ||
|
||
/** | ||
* 通过限制用户的选择,来防止用户选择错误的变量 | ||
*/ | ||
export const getShouldChange = ({ | ||
collectionField, | ||
variables, | ||
localVariables, | ||
getAllCollectionsInheritChain, | ||
}: GetShouldChangeProps) => { | ||
const collectionsInheritChain = collectionField ? getAllCollectionsInheritChain(collectionField.target) : []; | ||
|
||
return async (value: any, optionPath: any[]) => { | ||
if (!optionPath) { | ||
return true; | ||
} | ||
if (_.isString(value) && value.includes('$nRole')) { | ||
return true; | ||
} | ||
|
||
if (!isVariable(value) || !variables || !collectionField) { | ||
return true; | ||
} | ||
|
||
// `json` 可以选择任意类型的变量,详见:https://nocobase.feishu.cn/docx/EmNEdEBOnoQohUx2UmBcqIQ5nyh#FPLfdSRDEoXR65xW0mBcdfL5n0c | ||
if (collectionField.interface === 'json') { | ||
return true; | ||
} | ||
|
||
const lastOption = optionPath[optionPath.length - 1]; | ||
|
||
// 点击叶子节点时,必须更新 value | ||
if (lastOption && _.isEmpty(lastOption.children) && !lastOption.loadChildren) { | ||
return true; | ||
} | ||
|
||
const collectionFieldOfVariable = await variables.getCollectionField(value, localVariables); | ||
|
||
if (!collectionField) { | ||
return false; | ||
} | ||
|
||
// `一对一` 和 `一对多` 的不能用于设置默认值,因为其具有唯一性 | ||
if (['o2o', 'o2m', 'oho'].includes(collectionFieldOfVariable?.interface)) { | ||
return false; | ||
} | ||
if (!collectionField.target && collectionFieldOfVariable?.target) { | ||
return false; | ||
} | ||
if (collectionField.target && !collectionFieldOfVariable?.target) { | ||
return false; | ||
} | ||
if ( | ||
collectionField.target && | ||
collectionFieldOfVariable?.target && | ||
!collectionsInheritChain.includes(collectionFieldOfVariable?.target) | ||
) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}; | ||
}; | ||
|
||
export interface FormatVariableScopeParam { | ||
children: any[]; | ||
disabled: boolean; | ||
name: string; | ||
title: string; | ||
} | ||
|
||
export interface FormatVariableScopeReturn { | ||
value: string; | ||
key: string; | ||
label: string; | ||
disabled: boolean; | ||
children?: any[]; | ||
} | ||
|
||
/** | ||
* 兼容老版本的变量 | ||
* @param variables | ||
*/ | ||
export function useCompatOldVariables(props: { | ||
uiSchema: any; | ||
collectionField: CollectionFieldOptions_deprecated; | ||
blockCollectionName: string; | ||
noDisabled?: boolean; | ||
targetFieldSchema?: Schema; | ||
}) { | ||
const { uiSchema, collectionField, noDisabled, targetFieldSchema, blockCollectionName } = props; | ||
const { t } = useTranslation(); | ||
const { currentUserSettings } = useCurrentUserVariable({ | ||
maxDepth: 1, | ||
uiSchema: uiSchema, | ||
collectionField, | ||
noDisabled, | ||
targetFieldSchema, | ||
}); | ||
const { currentRecordSettings } = useCurrentRecordVariable({ | ||
schema: uiSchema, | ||
collectionField, | ||
noDisabled, | ||
targetFieldSchema, | ||
}); | ||
|
||
const compatOldVariables = useCallback( | ||
(variables: Option[], { value }) => { | ||
if (!isVariable(value)) { | ||
return variables; | ||
} | ||
|
||
variables = [...variables]; | ||
|
||
const systemVariable: Option = { | ||
value: '$system', | ||
key: '$system', | ||
label: t('System variables'), | ||
isLeaf: false, | ||
children: [ | ||
{ | ||
value: 'now', | ||
key: 'now', | ||
label: t('Current time'), | ||
isLeaf: true, | ||
depth: 1, | ||
}, | ||
], | ||
depth: 0, | ||
}; | ||
const currentTime = { | ||
value: 'currentTime', | ||
label: t('Current time'), | ||
children: null, | ||
}; | ||
|
||
if (value.includes('$system')) { | ||
variables.push(systemVariable); | ||
} | ||
|
||
if (value.includes(`${blockCollectionName}.`)) { | ||
const variable = variables.find((item) => item.value === '$nForm' || item.value === '$nRecord'); | ||
if (variable) { | ||
variable.value = blockCollectionName; | ||
} | ||
} | ||
|
||
if (value.includes('$form')) { | ||
const variable = variables.find((item) => item.value === '$nForm'); | ||
if (variable) { | ||
variable.value = '$form'; | ||
} | ||
} | ||
|
||
if (value.includes('currentUser')) { | ||
const userVariable = variables.find((item) => item.value === '$user'); | ||
if (userVariable) { | ||
userVariable.value = 'currentUser'; | ||
} else { | ||
variables.unshift({ ...currentUserSettings, value: 'currentUser' }); | ||
} | ||
} | ||
|
||
if (value.includes('currentRecord')) { | ||
const formVariable = variables.find((item) => item.value === '$nRecord'); | ||
if (formVariable) { | ||
formVariable.value = 'currentRecord'; | ||
} else { | ||
variables.unshift({ ...currentRecordSettings, value: 'currentRecord' }); | ||
} | ||
} | ||
|
||
if (value.includes('currentTime')) { | ||
variables.push(currentTime); | ||
} | ||
|
||
if (value.includes('$date')) { | ||
const formVariable = variables.find((item) => item.value === '$nDate'); | ||
if (formVariable) { | ||
formVariable.value = '$date'; | ||
} | ||
} | ||
|
||
return variables; | ||
}, | ||
[blockCollectionName], | ||
); | ||
|
||
return { compatOldVariables }; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感觉不复制一份,在原来的基础上修改就可以,这样也可以防止冗余代码