import { css } from '@emotion/css'; import { ArrayCollapse, ArrayItems, FormItem, FormLayout, Input } from '@formily/antd-v5'; import { Field, GeneralField, createForm } from '@formily/core'; import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react'; import { uid } from '@formily/shared'; import { error } from '@nocobase/utils/client'; import type { DropdownProps } from 'antd'; import { Alert, App, Button, Cascader, CascaderProps, ConfigProvider, Dropdown, Empty, MenuItemProps, MenuProps, Modal, ModalFuncProps, Space, Switch, } from 'antd'; import _, { cloneDeep, get, set } from 'lodash'; import React, { FC, ReactNode, createContext, useCallback, useContext, useEffect, useMemo, // @ts-ignore useTransition as useReactTransition, useState, } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; import { Router } from 'react-router-dom'; import { APIClientProvider, ActionContextProvider, AssociationOrCollectionProvider, CollectionFieldOptions_deprecated, CollectionRecordProvider, DataSourceApplicationProvider, Designable, FormDialog, FormProvider, RemoteSchemaComponent, SchemaComponent, SchemaComponentContext, SchemaComponentOptions, createDesignable, findFormBlock, useAPIClient, useBlockRequestContext, useCollectionManager_deprecated, useCollectionRecord, useCollection_deprecated, useCompile, useDataBlockProps, useDesignable, useFilterBlock, useGlobalTheme, useLinkageCollectionFilterOptions, useRecord, useSchemaSettingsItem, useSortFields, } from '..'; import { BlockRequestContext_deprecated, FormBlockContext, useFormBlockContext, useFormBlockType, useTableBlockContext, } from '../block-provider'; import { FormActiveFieldsProvider, findFilterTargets, updateFilterTargets, useFormActiveFields, } from '../block-provider/hooks'; import { SelectWithTitle, SelectWithTitleProps } from '../common/SelectWithTitle'; import { useNiceDropdownMaxHeight } from '../common/useNiceDropdownHeight'; import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider'; import { useDataSourceKey } from '../data-source/data-source/DataSourceProvider'; import { FilterBlockType, getSupportFieldsByAssociation, getSupportFieldsByForeignKey, isSameCollection, useSupportedBlocks, } from '../filter-provider/utils'; import { FlagProvider } from '../flag-provider'; import { useCollectMenuItem, useCollectMenuItems, useMenuItem } from '../hooks/useMenuItem'; import { SubFormProvider, useSubFormValue } from '../schema-component/antd/association-field/hooks'; import { getTargetKey } from '../schema-component/antd/association-filter/utilts'; import { useSchemaTemplateManager } from '../schema-templates'; import { useBlockTemplateContext } from '../schema-templates/BlockTemplate'; import { useLocalVariables, useVariables } from '../variables'; import { FormDataTemplates } from './DataTemplates'; import { EnableChildCollections } from './EnableChildCollections'; import { ChildDynamicComponent } from './EnableChildCollections/DynamicComponent'; import { FormLinkageRules } from './LinkageRules'; import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks'; export interface SchemaSettingsProps { title?: any; dn?: Designable; field?: GeneralField; fieldSchema?: Schema; children?: ReactNode; } interface SchemaSettingsContextProps { dn?: Designable; field?: GeneralField; fieldSchema?: Schema; setVisible?: any; visible?: any; template?: any; collectionName?: any; designer?: T; } const SchemaSettingsContext = createContext(null); SchemaSettingsContext.displayName = 'SchemaSettingsContext'; export function useSchemaSettings() { return useContext(SchemaSettingsContext) as SchemaSettingsContextProps; } interface SchemaSettingsProviderProps { dn?: Designable; field?: GeneralField; fieldSchema?: Schema; setVisible?: any; visible?: any; template?: any; collectionName?: any; designer?: any; } export const SchemaSettingsProvider: React.FC = (props) => { const { children, fieldSchema, ...others } = props; const { getTemplateBySchema } = useSchemaTemplateManager(); const { name } = useCollection_deprecated(); const template = getTemplateBySchema(fieldSchema); return ( {children} ); }; export const SchemaSettingsDropdown: React.FC = (props) => { const { title, dn, ...others } = props; const [visible, setVisible] = useState(false); const { Component, getMenuItems } = useMenuItem(); const [, startTransition] = useReactTransition(); const dropdownMaxHeight = useNiceDropdownMaxHeight([visible]); const changeMenu: DropdownProps['onOpenChange'] = useCallback((nextOpen: boolean, info) => { if (info.source === 'trigger' || nextOpen) { // 当鼠标快速滑过时,终止菜单的渲染,防止卡顿 startTransition(() => { setVisible(nextOpen); }); } }, []); const items = getMenuItems(() => props.children); return (
{typeof title === 'string' ? {title} : title}
); }; export const SchemaSettingsTemplate = function Template(props) { const { componentName, collectionName, resourceName, needRender } = props; const { t } = useTranslation(); const { getCollection } = useCollectionManager_deprecated(); const { dn, setVisible, template, fieldSchema } = useSchemaSettings(); const compile = useCompile(); const api = useAPIClient(); const { dn: tdn } = useBlockTemplateContext(); const { saveAsTemplate, copyTemplateSchema } = useSchemaTemplateManager(); const { theme } = useGlobalTheme(); if (!collectionName && !needRender) { return null; } if (template) { return ( { const schema = await copyTemplateSchema(template); const removed = tdn.removeWithoutEmit(); tdn.insertAfterEnd(schema, { async onSuccess() { await api.request({ url: `/uiSchemas:remove/${removed['x-uid']}`, }); }, }); }} > {t('Convert reference to duplicate')} ); } return ( { setVisible(false); const collection = collectionName && getCollection(collectionName); const values = await FormDialog( t('Save as template'), () => { return ( ); }, theme, ).open({}); const sdn = createDesignable({ t, api, refresh: dn.refresh.bind(dn), current: fieldSchema.parent, }); sdn.loadAPIClientEvents(); const { key } = await saveAsTemplate({ collectionName, resourceName, componentName, dataSourceKey: collection.dataSource, name: values.name, uid: fieldSchema['x-uid'], }); sdn.removeWithoutEmit(fieldSchema); sdn.insertBeforeEnd({ type: 'void', 'x-component': 'BlockTemplate', 'x-component-props': { templateId: key, }, }); }} > {t('Save as template')} ); }; const findGridSchema = (fieldSchema) => { return fieldSchema.reduceProperties((buf, s) => { if (s['x-component'] === 'FormV2' || s['x-component'] === 'Details') { const f = s.reduceProperties((buf, s) => { if (s['x-component'] === 'Grid' || s['x-component'] === 'BlockTemplate') { return s; } return buf; }, null); if (f) { return f; } } return buf; }, null); }; const findBlockTemplateSchema = (fieldSchema) => { return fieldSchema.reduceProperties((buf, s) => { if (s['x-component'] === 'FormV2' || s['x-component'] === 'Details') { const f = s.reduceProperties((buf, s) => { if (s['x-component'] === 'BlockTemplate') { return s; } return buf; }, null); if (f) { return f; } } return buf; }, null); }; export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) { const { insertAdjacentPosition = 'afterBegin', componentName, collectionName, resourceName } = props; const { t } = useTranslation(); const compile = useCompile(); const { getCollection } = useCollectionManager_deprecated(); const { dn, setVisible, template, fieldSchema } = useSchemaSettings(); const api = useAPIClient(); const { saveAsTemplate, copyTemplateSchema } = useSchemaTemplateManager(); const { theme } = useGlobalTheme(); if (!collectionName) { return null; } if (template) { return ( { const schema = await copyTemplateSchema(template); const templateSchema = findBlockTemplateSchema(fieldSchema); const sdn = createDesignable({ t, api, refresh: dn.refresh.bind(dn), current: templateSchema.parent, }); sdn.loadAPIClientEvents(); sdn.removeWithoutEmit(templateSchema); sdn.insertAdjacent(insertAdjacentPosition, schema, { async onSuccess() { await api.request({ url: `/uiSchemas:remove/${templateSchema['x-uid']}`, }); }, }); fieldSchema['x-template-key'] = null; await api.request({ url: `uiSchemas:patch`, method: 'post', data: { 'x-uid': fieldSchema['x-uid'], 'x-template-key': null, }, }); dn.refresh(); }} > {t('Convert reference to duplicate')} ); } return ( { setVisible(false); const collection = collectionName && getCollection(collectionName); const gridSchema = findGridSchema(fieldSchema); const values = await FormDialog( t('Save as template'), () => { const componentTitle = { FormItem: t('Form'), ReadPrettyFormItem: t('Details'), }; return ( ); }, theme, ).open({}); const sdn = createDesignable({ t, api, refresh: dn.refresh.bind(dn), current: gridSchema.parent, }); sdn.loadAPIClientEvents(); const { key } = await saveAsTemplate({ collectionName, resourceName, componentName, dataSourceKey: collection.dataSource, name: values.name, uid: gridSchema['x-uid'], }); sdn.removeWithoutEmit(gridSchema); sdn.insertAdjacent(insertAdjacentPosition, { type: 'void', 'x-component': 'BlockTemplate', 'x-component-props': { templateId: key, }, }); fieldSchema['x-template-key'] = key; await api.request({ url: `uiSchemas:patch`, method: 'post', data: { 'x-uid': fieldSchema['x-uid'], 'x-template-key': key, }, }); }} > {t('Save as block template')} ); }; export interface SchemaSettingsItemProps extends Omit { title: string; } export const SchemaSettingsItem: FC = (props) => { const { pushMenuItem } = useCollectMenuItems(); const { collectMenuItem } = useCollectMenuItem(); const { eventKey, title } = props; const { name } = useSchemaSettingsItem(); if (process.env.NODE_ENV !== 'production' && !title) { throw new Error('SchemaSettingsItem must have a title'); } const item = { key: title, ..._.omit(props, ['children', 'name']), eventKey: eventKey as any, onClick: (info) => { info.domEvent.preventDefault(); info.domEvent.stopPropagation(); props?.onClick?.(info); }, style: { minWidth: 120 }, label: props.children || props.title, title: props.title, } as MenuProps['items'][0]; pushMenuItem?.(item); collectMenuItem?.(item); return null; }; export interface SchemaSettingsItemGroupProps { title: string; children: any[]; } export const SchemaSettingsItemGroup: FC = (props) => { const { Component, getMenuItems } = useMenuItem(); const { pushMenuItem } = useCollectMenuItems(); const key = useMemo(() => uid(), []); const item = { key, type: 'group', title: props.title, label: props.title, children: getMenuItems(() => props.children), } as MenuProps['items'][0]; pushMenuItem(item); return ; }; export interface SchemaSettingsSubMenuProps { title: string; eventKey?: string; children: any; } export const SchemaSettingsSubMenu = function SubMenu(props: SchemaSettingsSubMenuProps) { const { Component, getMenuItems } = useMenuItem(); const { pushMenuItem } = useCollectMenuItems(); const key = useMemo(() => uid(), []); const item = { key, label: props.title, title: props.title, children: getMenuItems(() => props.children), } as MenuProps['items'][0]; pushMenuItem(item); return ; }; export const SchemaSettingsDivider = function Divider() { const { pushMenuItem } = useCollectMenuItems(); const key = useMemo(() => uid(), []); const item = { key, type: 'divider', } as MenuProps['items'][0]; pushMenuItem(item); return null; }; export interface SchemaSettingsRemoveProps { confirm?: ModalFuncProps; removeParentsIfNoChildren?: boolean; breakRemoveOn?: ISchema | ((s: ISchema) => boolean); } export const SchemaSettingsRemove: FC = (props) => { const { confirm, removeParentsIfNoChildren, breakRemoveOn } = props; const { dn, template } = useSchemaSettings(); const { t } = useTranslation(); const field = useField(); const fieldSchema = useFieldSchema(); const ctx = useBlockTemplateContext(); const form = useForm(); const { modal } = App.useApp(); const { removeActiveFieldName } = useFormActiveFields() || {}; const { removeDataBlock } = useFilterBlock(); return ( { modal.confirm({ title: t('Delete block'), content: t('Are you sure you want to delete it?'), ...confirm, async onOk() { const options = { removeParentsIfNoChildren, breakRemoveOn, }; if (field?.required) { field.required = false; fieldSchema['required'] = false; } if (template && template.uid === fieldSchema['x-uid'] && ctx?.dn) { await ctx?.dn.remove(null, options); } else { await dn.remove(null, options); } await confirm?.onOk?.(); delete form.values[fieldSchema.name]; removeActiveFieldName?.(fieldSchema.name as string); if (field?.setInitialValue && field?.reset) { field.setInitialValue(null); field.reset(); } removeDataBlock(fieldSchema['x-uid']); }, }); }} > {t('Delete')} ); }; interface SchemaSettingsConnectDataBlocksProps { type: FilterBlockType; emptyDescription?: string; } export const SchemaSettingsConnectDataBlocks: FC = (props) => { const { type, emptyDescription } = props; const fieldSchema = useFieldSchema(); const { dn } = useDesignable(); const { t } = useTranslation(); const collection = useCollection_deprecated(); const { inProvider } = useFilterBlock(); const dataBlocks = useSupportedBlocks(type); // eslint-disable-next-line prefer-const let { targets = [], uid } = findFilterTargets(fieldSchema); const compile = useCompile(); const { getAllCollectionsInheritChain } = useCollectionManager_deprecated(); if (!inProvider) { return null; } const Content = dataBlocks.map((block) => { const title = `${compile(block.collection.title)} #${block.uid.slice(0, 4)}`; const onHover = () => { const dom = block.dom; const designer = dom.querySelector('.general-schema-designer') as HTMLElement; if (designer) { designer.style.display = 'block'; } dom.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)'; dom.scrollIntoView({ behavior: 'smooth', block: 'center', }); }; const onLeave = () => { const dom = block.dom; const designer = dom.querySelector('.general-schema-designer') as HTMLElement; if (designer) { designer.style.display = null; } dom.style.boxShadow = 'none'; }; if (isSameCollection(block.collection, collection)) { return ( target.uid === block.uid)} onChange={(checked) => { if (checked) { targets.push({ uid: block.uid }); } else { targets = targets.filter((target) => target.uid !== block.uid); block.clearFilter(uid); } updateFilterTargets(fieldSchema, targets); dn.emit('patch', { schema: { ['x-uid']: uid, 'x-filter-targets': targets, }, }).catch(error); dn.refresh(); }} onMouseEnter={onHover} onMouseLeave={onLeave} /> ); } const target = targets.find((target) => target.uid === block.uid); // 与筛选区块的数据表具有关系的表 return ( { return { label: compile(field.uiSchema.title) || field.name, value: `${field.name}.${getTargetKey(field)}`, }; }), ...getSupportFieldsByForeignKey(collection, block).map((field) => { return { label: `${compile(field.uiSchema.title) || field.name} [${t('Foreign key')}]`, value: field.name, }; }), { label: t('Unconnected'), value: '', }, ]} onChange={(value) => { if (value === '') { targets = targets.filter((target) => target.uid !== block.uid); block.clearFilter(uid); } else { targets = targets.filter((target) => target.uid !== block.uid); targets.push({ uid: block.uid, field: value }); } updateFilterTargets(fieldSchema, targets); dn.emit('patch', { schema: { ['x-uid']: uid, 'x-filter-targets': targets, }, }); dn.refresh(); }} onMouseEnter={onHover} onMouseLeave={onLeave} /> ); }); return ( {Content.length ? ( Content ) : ( )} ); }; export interface SchemaSettingsSelectItemProps extends Omit, Omit { value?: SelectWithTitleProps['defaultValue']; } export const SchemaSettingsSelectItem: FC = (props) => { const { title, options, value, onChange, ...others } = props; return ( ); }; export type SchemaSettingsCascaderItemProps = CascaderProps & Omit & { title: any }; export const SchemaSettingsCascaderItem: FC = (props) => { const { title, options, value, onChange, ...others } = props; return (
{title}
); }; export interface SchemaSettingsSwitchItemProps extends Omit { title: string; checked?: boolean; onChange?: (v: boolean) => void; } export const SchemaSettingsSwitchItem: FC = (props) => { const { title, onChange, ...others } = props; const [checked, setChecked] = useState(!!props.checked); return ( { onChange?.(!checked); setChecked(!checked); }} >
{title}
); }; export interface SchemaSettingsPopupProps extends SchemaSettingsItemProps { schema?: ISchema; } export const SchemaSettingsPopupItem: FC = (props) => { const { schema, ...others } = props; const [visible, setVisible] = useState(false); const ctx = useContext(SchemaSettingsContext); return ( { // actx.setVisible(false); ctx.setVisible(false); setVisible(true); }} > {props.children || props.title} ); }; export interface SchemaSettingsActionModalItemProps extends SchemaSettingsModalItemProps, Omit { uid?: string; initialSchema?: ISchema; schema?: ISchema; beforeOpen?: () => void; maskClosable?: boolean; } export const SchemaSettingsActionModalItem: FC = React.memo((props) => { const { title, onSubmit, initialValues, beforeOpen, initialSchema, schema, modalTip, components, scope, ...others } = props; const [visible, setVisible] = useState(false); const [schemaUid, setSchemaUid] = useState(props.uid); const { t } = useTranslation(); const fieldSchema = useFieldSchema(); const ctx = useContext(SchemaSettingsContext); const { dn } = useSchemaSettings(); const compile = useCompile(); const api = useAPIClient(); const upLevelActiveFields = useFormActiveFields(); const form = useMemo( () => createForm({ initialValues: cloneDeep(initialValues), values: cloneDeep(initialValues), }), [initialValues], ); useEffect(() => { form.setInitialValues(cloneDeep(initialValues)); }, [JSON.stringify(initialValues || {})]); const cancelHandler = useCallback(() => { setVisible(false); form.reset(); }, [form]); const submitHandler = useCallback(async () => { await form.submit(); onSubmit?.(cloneDeep(form.values)); setVisible(false); }, [form, onSubmit]); const openAssignedFieldValueHandler = useCallback(async () => { if (!schemaUid && initialSchema?.['x-uid']) { fieldSchema['x-action-settings'].schemaUid = initialSchema['x-uid']; dn.emit('patch', { schema: fieldSchema }); await api.resource('uiSchemas').insert({ values: initialSchema }); setSchemaUid(initialSchema['x-uid']); } if (typeof beforeOpen === 'function') { beforeOpen?.(); } ctx.setVisible(false); setVisible(true); }, [api, ctx, dn, fieldSchema, initialSchema, schemaUid]); const onKeyDown = useCallback((e: React.KeyboardEvent): void => e.stopPropagation(), []); return ( <> {props.children || props.title} {createPortal( } > {modalTip && } {modalTip &&
} {visible && schemaUid && ( )} {visible && schema && }
, document.body, )} ); }); SchemaSettingsActionModalItem.displayName = 'SchemaSettingsActionModalItem'; export interface SchemaSettingsModalItemProps { title: string; onSubmit: (values: any) => void; initialValues?: any; schema?: ISchema | (() => ISchema); modalTip?: string; components?: any; hidden?: boolean; scope?: any; effects?: any; width?: string | number; children?: ReactNode; asyncGetInitialValues?: () => Promise; eventKey?: string; hide?: boolean; } export const SchemaSettingsModalItem: FC = (props) => { const { hidden, title, components, scope, effects, onSubmit, asyncGetInitialValues, initialValues, width = 'fit-content', ...others } = props; const options = useContext(SchemaOptionsContext); const collection = useCollection_deprecated(); const apiClient = useAPIClient(); const { theme } = useGlobalTheme(); const ctx = useBlockRequestContext(); const upLevelActiveFields = useFormActiveFields(); const { locale } = useContext(ConfigProvider.ConfigContext); const dm = useDataSourceManager(); const dataSourceKey = useDataSourceKey(); const record = useCollectionRecord(); const { association } = useDataBlockProps() || {}; const formCtx = useFormBlockContext(); // 解决变量`当前对象`值在弹窗中丢失的问题 const { formValue: subFormValue, collection: subFormCollection } = useSubFormValue(); if (hidden) { return null; } return ( { const values = asyncGetInitialValues ? await asyncGetInitialValues() : initialValues; const schema = _.isFunction(props.schema) ? props.schema() : props.schema; FormDialog( { title: schema.title || title, width }, () => { return ( 576px @media (min-width: 576px) { min-width: 520px; } // screen <= 576px @media (max-width: 576px) { min-width: 320px; } `} > ); }, theme, ) .open({ initialValues: values, effects, }) .then((values) => { onSubmit(values); return values; }) .catch((err) => { console.error(err); }); }} > {props.children || props.title} ); }; export const SchemaSettingsBlockTitleItem = function BlockTitleItem() { const field = useField(); const fieldSchema = useFieldSchema(); const { dn } = useDesignable(); const { t } = useTranslation(); return ( { const componentProps = fieldSchema['x-component-props'] || {}; componentProps.title = title; fieldSchema['x-component-props'] = componentProps; field.componentProps.title = title; dn.emit('patch', { schema: { ['x-uid']: fieldSchema['x-uid'], 'x-component-props': fieldSchema['x-component-props'], }, }); dn.refresh(); }} /> ); }; export const SchemaSettingsDefaultSortingRules = function DefaultSortingRules(props) { const { path = 'x-component-props.params.sort' } = props; const { t } = useTranslation(); const { dn } = useDesignable(); const fieldSchema = useFieldSchema(); const field = useField(); const title = props.title || t('Set default sorting rules'); const { name } = useCollection_deprecated(); const defaultSort = get(fieldSchema, path) || []; const sort = defaultSort?.map((item: string) => { return item.startsWith('-') ? { field: item.substring(1), direction: 'desc', } : { field: item, direction: 'asc', }; }); const sortFields = useSortFields(props.name || name); const onSubmit = async ({ sort }) => { if (props?.onSubmit) { return props.onSubmit({ sort }); } const value = sort.map((item) => { return item.direction === 'desc' ? `-${item.field}` : item.field; }); set( field, path.replace('x-component-props', 'componentProps').replace('x-decorator-props', 'decoratorProps'), value, ); set(fieldSchema, path, value); await dn.emit('patch', { schema: fieldSchema, }); return props.onSubmitAfter?.(); }; return ( ); }; export const SchemaSettingsLinkageRules = function LinkageRules(props) { const { collectionName } = props; const fieldSchema = useFieldSchema(); const { form } = useFormBlockContext(); const { dn } = useDesignable(); const { t } = useTranslation(); const { getTemplateById } = useSchemaTemplateManager(); const variables = useVariables(); const localVariables = useLocalVariables(); const record = useRecord(); const { type: formBlockType } = useFormBlockType(); const type = props?.type || ['Action', 'Action.Link'].includes(fieldSchema['x-component']) ? 'button' : 'field'; const gridSchema = findGridSchema(fieldSchema) || fieldSchema; const schema = useMemo( () => ({ type: 'object', title: t('Linkage rules'), properties: { fieldReaction: { 'x-component': FormLinkageRules, 'x-use-component-props': () => { // eslint-disable-next-line react-hooks/rules-of-hooks const options = useLinkageCollectionFilterOptions(collectionName); return { options, defaultValues: gridSchema?.['x-linkage-rules'] || fieldSchema?.['x-linkage-rules'], type, // eslint-disable-next-line react-hooks/rules-of-hooks linkageOptions: useLinkageCollectionFieldOptions(collectionName), collectionName, form, variables, localVariables, record, formBlockType, }; }, }, }, }), [collectionName, fieldSchema, form, gridSchema, localVariables, record, t, type, variables], ); const components = useMemo(() => ({ ArrayCollapse, FormLayout }), []); const onSubmit = useCallback( (v) => { const rules = []; for (const rule of v.fieldReaction.rules) { rules.push(_.pickBy(rule, _.identity)); } const templateId = gridSchema['x-component'] === 'BlockTemplate' && gridSchema['x-component-props']?.templateId; const uid = (templateId && getTemplateById(templateId).uid) || gridSchema['x-uid']; const schema = { ['x-uid']: uid, }; gridSchema['x-linkage-rules'] = rules; schema['x-linkage-rules'] = rules; dn.emit('patch', { schema, }); dn.refresh(); }, [dn, getTemplateById, gridSchema], ); return ( ); }; export const useDataTemplates = (schema?: Schema) => { const fieldSchema = useFieldSchema(); if (schema) { return { templateData: _.cloneDeep(schema['x-data-templates']), }; } const formSchema = findFormBlock(fieldSchema) || fieldSchema; return { templateData: _.cloneDeep(formSchema?.['x-data-templates']), }; }; export const SchemaSettingsDataTemplates = function DataTemplates(props) { const designerCtx = useContext(SchemaComponentContext); const { collectionName } = props; const fieldSchema = useFieldSchema(); const { dn } = useDesignable(); const { t } = useTranslation(); const formSchema = findFormBlock(fieldSchema) || fieldSchema; const { templateData } = useDataTemplates(); const schema = useMemo( () => ({ type: 'object', title: t('Form data templates'), properties: { fieldReaction: { 'x-decorator': (props) => , 'x-component': FormDataTemplates, 'x-use-component-props': () => { return { defaultValues: templateData, collectionName, }; }, 'x-component-props': { designerCtx, formSchema, }, }, }, }), [templateData], ); const onSubmit = useCallback((v) => { const data = { ...(formSchema['x-data-templates'] || {}), ...v.fieldReaction }; // 当 Tree 组件开启 checkStrictly 属性时,会导致 checkedKeys 的值是一个对象,而不是数组,所以这里需要转换一下以支持旧版本 data.items.forEach((item) => { item.fields = Array.isArray(item.fields) ? item.fields : item.fields.checked; }); const schema = { ['x-uid']: formSchema['x-uid'], ['x-data-templates']: data, }; formSchema['x-data-templates'] = data; dn.emit('patch', { schema, }); dn.refresh(); }, []); const title = useMemo(() => t('Form data templates'), []); const components = useMemo(() => ({ ArrayCollapse, FormLayout }), []); return ( ); }; export function SchemaSettingsEnableChildCollections(props) { const { collectionName } = props; const fieldSchema = useFieldSchema(); const field = useField(); const { dn } = useDesignable(); const { t } = useTranslation(); const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current']; const { getCollectionJoinField } = useCollectionManager_deprecated(); const ctx = useBlockRequestContext(); const collectionField = getCollectionJoinField(fieldSchema?.parent?.['x-collection-field']) || {}; const isAssocationAdd = fieldSchema?.parent?.['x-component'] === 'CollectionField'; return ( { return { defaultValues: fieldSchema?.['x-enable-children'], collectionName, }; }, }, allowAddToCurrent: { type: 'boolean', 'x-content': "{{t('Allow adding records to the current collection')}}", 'x-decorator': 'FormItem', 'x-component': 'Checkbox', default: allowAddToCurrent === undefined ? true : allowAddToCurrent, }, linkageFromForm: { type: 'string', title: "{{t('Linkage with form fields')}}", 'x-visible': '{{isAssocationAdd}}', 'x-decorator': 'FormItem', 'x-component': ChildDynamicComponent, 'x-component-props': { rootCollection: ctx.props.collection || ctx.props.resource, collectionField, }, default: fieldSchema?.['x-component-props']?.['linkageFromForm'], }, }, } as ISchema } onSubmit={(v) => { const enableChildren = []; for (const item of v.enableChildren.childrenCollections) { enableChildren.push(_.pickBy(item, _.identity)); } const uid = fieldSchema['x-uid']; const schema = { ['x-uid']: uid, }; fieldSchema['x-enable-children'] = enableChildren; fieldSchema['x-allow-add-to-current'] = v.allowAddToCurrent; fieldSchema['x-component-props'] = { ...fieldSchema['x-component-props'], component: 'CreateRecordAction', linkageFromForm: v?.linkageFromForm, }; schema['x-enable-children'] = enableChildren; schema['x-allow-add-to-current'] = v.allowAddToCurrent; schema['x-component-props'] = { ...fieldSchema['x-component-props'], component: 'CreateRecordAction', linkageFromForm: v?.linkageFromForm, }; field.componentProps['linkageFromForm'] = v.linkageFromForm; dn.emit('patch', { schema, }); dn.refresh(); }} /> ); } export const defaultInputStyle = css` & > .nb-form-item { flex: 1; } `; export const findParentFieldSchema = (fieldSchema: Schema) => { let parent = fieldSchema.parent; while (parent) { if (parent['x-component'] === 'CollectionField') { return parent; } parent = parent.parent; } }; export const SchemaSettingsSortField = () => { const { fields } = useCollection_deprecated(); const field = useField(); const fieldSchema = useFieldSchema(); const { t } = useTranslation(); const { dn } = useDesignable(); const compile = useCompile(); const { service } = useTableBlockContext(); const options = fields .filter((field) => !field?.target && field.interface === 'sort') .map((field) => ({ value: field?.name, label: compile(field?.uiSchema?.title) || field?.name, })); return ( { fieldSchema['x-decorator-props'].dragSortBy = dragSortBy; service.run({ ...service.params?.[0], sort: dragSortBy }); field.decoratorProps.dragSortBy = dragSortBy; dn.emit('patch', { schema: { ['x-uid']: fieldSchema['x-uid'], 'x-decorator-props': fieldSchema['x-decorator-props'], }, }); dn.refresh(); }} /> ); }; // 是否是系统字段 export const isSystemField = (collectionField: CollectionFieldOptions_deprecated, getInterface) => { const i = getInterface?.(collectionField?.interface); return i?.group === 'systemInfo'; }; export function getFieldDefaultValue(fieldSchema: ISchema, collectionField: CollectionFieldOptions_deprecated) { const result = fieldSchema?.default ?? collectionField?.defaultValue; return result; }