feat(association-field): association field support sub-form(popover) (#2373)
* refactor: association field support sub-form(popover) * refactor: code improve * refactor: code improve * refactor: sub-table support field component cinfig * refactor: sub-table support field component cinfig * refactor: sub-table support field component cinfig * refactor: code improve * refactor: code improve * refactor: useAssociationNames * refactor: locale improve * refactor: locale improve * refactor: code improve * refactor: code improve * refactor: code improve * fix: code improve * refactor: code refactor * style: style improve * Update database.ts * refactor: code imporve * refactor: code imporve * style: style improve * style: style improve * refactor: code imporve * style: style improve * refactor: code imporve --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
		
							parent
							
								
									d84c52618d
								
							
						
					
					
						commit
						d333ad201a
					
				@ -151,12 +151,8 @@ export const useCreateActionProps = () => {
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
      const fieldNames = fields.map((field) => field.name);
 | 
			
		||||
      const {
 | 
			
		||||
        assignedValues: originalAssignedValues = {},
 | 
			
		||||
        onSuccess,
 | 
			
		||||
        overwriteValues,
 | 
			
		||||
        skipValidator,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } =
 | 
			
		||||
        actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const addChild = fieldSchema?.['x-component-props']?.addChild;
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
      if (!skipValidator) {
 | 
			
		||||
@ -226,12 +222,8 @@ export const useAssociationCreateActionProps = () => {
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
      const fieldNames = fields.map((field) => field.name);
 | 
			
		||||
      const {
 | 
			
		||||
        assignedValues: originalAssignedValues = {},
 | 
			
		||||
        onSuccess,
 | 
			
		||||
        overwriteValues,
 | 
			
		||||
        skipValidator,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } =
 | 
			
		||||
        actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const addChild = fieldSchema?.['x-component-props']?.addChild;
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
      if (!skipValidator) {
 | 
			
		||||
@ -415,11 +407,8 @@ export const useCustomizeUpdateActionProps = () => {
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
      const {
 | 
			
		||||
        assignedValues: originalAssignedValues = {},
 | 
			
		||||
        onSuccess,
 | 
			
		||||
        skipValidator,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const { assignedValues: originalAssignedValues = {}, onSuccess, skipValidator } =
 | 
			
		||||
        actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
      if (skipValidator === false) {
 | 
			
		||||
        await form.submit();
 | 
			
		||||
@ -473,11 +462,8 @@ export const useCustomizeBulkUpdateActionProps = () => {
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
      const {
 | 
			
		||||
        assignedValues: originalAssignedValues = {},
 | 
			
		||||
        onSuccess,
 | 
			
		||||
        updateMode,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const { assignedValues: originalAssignedValues = {}, onSuccess, updateMode } =
 | 
			
		||||
        actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      actionField.data = field.data || {};
 | 
			
		||||
      actionField.data.loading = true;
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentUser });
 | 
			
		||||
@ -732,12 +718,8 @@ export const useUpdateActionProps = () => {
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
      const {
 | 
			
		||||
        assignedValues: originalAssignedValues = {},
 | 
			
		||||
        onSuccess,
 | 
			
		||||
        overwriteValues,
 | 
			
		||||
        skipValidator,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const { assignedValues: originalAssignedValues = {}, onSuccess, overwriteValues, skipValidator } =
 | 
			
		||||
        actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
      if (!skipValidator) {
 | 
			
		||||
        await form.submit();
 | 
			
		||||
@ -1124,7 +1106,7 @@ export const useAssociationNames = () => {
 | 
			
		||||
        const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name;
 | 
			
		||||
        const path = prefix === '' || !prefix ? fieldPath : prefix + '.' + fieldPath;
 | 
			
		||||
        appends.add(path);
 | 
			
		||||
        if (['Nester', 'SubTable'].includes(s['x-component-props']?.mode)) {
 | 
			
		||||
        if (['Nester', 'SubTable', 'PopoverNester'].includes(s['x-component-props']?.mode)) {
 | 
			
		||||
          updateAssociationValues.add(path);
 | 
			
		||||
          const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name;
 | 
			
		||||
          getAssociationAppends(s, bufPrefix);
 | 
			
		||||
 | 
			
		||||
@ -251,6 +251,7 @@ export default {
 | 
			
		||||
  "Link to description": "Used to create collection relationships quickly and compatible with most common scenarios. Suitable for non-developer use. When present as a field, it is a drop-down selection used to select records from the target collection. Once created, it will simultaneously generate the associated fields of the current collection in the target collection.",
 | 
			
		||||
  "Sub-table": "Sub-table",
 | 
			
		||||
  "Sub-details":"Sub-details",
 | 
			
		||||
  "Sub-form(Popover)":"Sub-form(Popover)",
 | 
			
		||||
  "System info": "System info",
 | 
			
		||||
  "Created at": "Created at",
 | 
			
		||||
  "Last updated at": "Last updated at",
 | 
			
		||||
 | 
			
		||||
@ -222,6 +222,7 @@ export default {
 | 
			
		||||
  "Link to": "リンク",
 | 
			
		||||
  "Link to description": "コレクションの関連付けを素早く作成するためにしようされ、ほとんどの一般的なシナリオに対応しています。開発者以外の方のしようにも適しています。フィールドとして存在する場合、参照元コレクションのレコードを選択するために使用されるドロップダウンです。一度作成されると、参照先コレクションに現在のコレクションの関連フィールドが同時に生成されます。",
 | 
			
		||||
  "Sub-table": "サブテーブル",
 | 
			
		||||
  "Sub-form(Popover)":"サブフォーム(ポップアップ窓)",
 | 
			
		||||
  "System info": "システム情報",
 | 
			
		||||
  "Created at": "作成日",
 | 
			
		||||
  "Last updated at": "最終更新日",
 | 
			
		||||
 | 
			
		||||
@ -317,6 +317,7 @@ export default {
 | 
			
		||||
  "Select file": "选择文件",
 | 
			
		||||
  "Subtable": "子表格",
 | 
			
		||||
  "Sub-form": "子表单",
 | 
			
		||||
  "Sub-form(Popover)":"子表单(弹窗)",
 | 
			
		||||
  "Sub-details":"子详情",
 | 
			
		||||
  "Record picker": "数据选择器",
 | 
			
		||||
  "Toggles the subfield mode": "切换子字段模式",
 | 
			
		||||
 | 
			
		||||
@ -28,19 +28,6 @@ export const AssociationFieldProvider = observer(
 | 
			
		||||
      [fieldSchema['x-component-props']?.mode],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // const targetKeyValue = useMemo(() => {
 | 
			
		||||
    //   if (!field.value) return '';
 | 
			
		||||
    //   if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
 | 
			
		||||
    //     return field.value[collectionField.targetKey] ?? '';
 | 
			
		||||
    //   }
 | 
			
		||||
    //   if (['belongsToMany', 'hasMany'].includes(collectionField.type)) {
 | 
			
		||||
    //     if (Array.isArray(field.value)) {
 | 
			
		||||
    //       return field.value.map((v) => v[collectionField.targetKey] ?? '').join(',');
 | 
			
		||||
    //     }
 | 
			
		||||
    //   }
 | 
			
		||||
    //   return '';
 | 
			
		||||
    // }, [collectionField, field.value]);
 | 
			
		||||
 | 
			
		||||
    const fieldValue = useMemo(() => JSON.stringify(field.value), [field.value]);
 | 
			
		||||
 | 
			
		||||
    const [loading, setLoading] = useState(true);
 | 
			
		||||
@ -52,7 +39,7 @@ export const AssociationFieldProvider = observer(
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      // 如果是表单模板数据,使用子表单和子表格组件时,过滤掉关系 ID
 | 
			
		||||
      if (field.value && field.form['__template'] && ['Nester', 'SubTable'].includes(currentMode)) {
 | 
			
		||||
      if (field.value && field.form['__template'] && ['Nester', 'SubTable', 'PopoverNester'].includes(currentMode)) {
 | 
			
		||||
        if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
 | 
			
		||||
          if (field.value?.[collectionField.targetKey]) {
 | 
			
		||||
            delete field.value[collectionField.targetKey];
 | 
			
		||||
@ -69,7 +56,7 @@ export const AssociationFieldProvider = observer(
 | 
			
		||||
      }
 | 
			
		||||
      if (field.value !== null && field.value !== undefined) {
 | 
			
		||||
        // Nester 子表单时,如果没数据初始化一个 [null] 的占位
 | 
			
		||||
        if (currentMode === 'Nester' && Array.isArray(field.value)) {
 | 
			
		||||
        if (['Nester', 'PopoverNester'].includes(currentMode) && Array.isArray(field.value)) {
 | 
			
		||||
          if (field.value.length === 0 && ['belongsToMany', 'hasMany'].includes(collectionField.type)) {
 | 
			
		||||
            field.value = [{}];
 | 
			
		||||
          }
 | 
			
		||||
@ -77,7 +64,7 @@ export const AssociationFieldProvider = observer(
 | 
			
		||||
        setLoading(false);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (currentMode === 'Nester') {
 | 
			
		||||
      if (['Nester', 'PopoverNester'].includes(currentMode)) {
 | 
			
		||||
        if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
 | 
			
		||||
          field.value = {};
 | 
			
		||||
        } else if (['belongsToMany', 'hasMany'].includes(collectionField.type)) {
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { InternalFileManager } from './FileManager';
 | 
			
		||||
import { InternalNester } from './InternalNester';
 | 
			
		||||
import { InternalPicker } from './InternalPicker';
 | 
			
		||||
import { InternalSubTable } from './InternalSubTable';
 | 
			
		||||
import { InternaPopoverNester } from './InternalPopoverNester';
 | 
			
		||||
import { CreateRecordAction } from './components/CreateRecordAction';
 | 
			
		||||
import { useAssociationFieldContext } from './hooks';
 | 
			
		||||
 | 
			
		||||
@ -45,6 +46,7 @@ const EditableAssociationField = observer(
 | 
			
		||||
      <SchemaComponentOptions scope={{ useCreateActionProps }} components={{ CreateRecordAction }}>
 | 
			
		||||
        {currentMode === 'Picker' && <InternalPicker {...props} />}
 | 
			
		||||
        {currentMode === 'Nester' && <InternalNester {...props} />}
 | 
			
		||||
        {currentMode === 'PopoverNester' && <InternaPopoverNester {...props} />}
 | 
			
		||||
        {currentMode === 'Select' && <AssociationSelect {...props} />}
 | 
			
		||||
        {currentMode === 'SubTable' && <InternalSubTable {...props} />}
 | 
			
		||||
        {currentMode === 'FileManager' && <InternalFileManager {...props} />}
 | 
			
		||||
 | 
			
		||||
@ -23,13 +23,9 @@ import { flatData, getLabelFormatValue, isShowFilePicker, useLabelUiSchema } fro
 | 
			
		||||
 | 
			
		||||
const useTableSelectorProps = () => {
 | 
			
		||||
  const field: any = useField();
 | 
			
		||||
  const {
 | 
			
		||||
    multiple,
 | 
			
		||||
    options = [],
 | 
			
		||||
    setSelectedRows,
 | 
			
		||||
    selectedRows: rcSelectRows = [],
 | 
			
		||||
    onChange,
 | 
			
		||||
  } = useContext(RecordPickerContext);
 | 
			
		||||
  const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext(
 | 
			
		||||
    RecordPickerContext,
 | 
			
		||||
  );
 | 
			
		||||
  const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
 | 
			
		||||
  const { setVisible } = useActionContext();
 | 
			
		||||
  return {
 | 
			
		||||
@ -72,6 +68,7 @@ const InternalFileManager = (props) => {
 | 
			
		||||
  const collectionField = getField(field.props.name);
 | 
			
		||||
  const labelUiSchema = useLabelUiSchema(collectionField?.target, fieldNames?.label || 'label');
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const { setVisible, modalProps } = useActionContext();
 | 
			
		||||
  const getFilter = () => {
 | 
			
		||||
    const targetKey = collectionField?.targetKey || 'id';
 | 
			
		||||
    const list = options.map((option) => option[targetKey]).filter(Boolean);
 | 
			
		||||
@ -119,7 +116,6 @@ const InternalFileManager = (props) => {
 | 
			
		||||
    collectionField,
 | 
			
		||||
  };
 | 
			
		||||
  const usePickActionProps = () => {
 | 
			
		||||
    const { setVisible } = useActionContext();
 | 
			
		||||
    const { multiple, selectedRows, onChange, options, collectionField } = useContext(RecordPickerContext);
 | 
			
		||||
    return {
 | 
			
		||||
      onClick() {
 | 
			
		||||
@ -159,7 +155,7 @@ const InternalFileManager = (props) => {
 | 
			
		||||
          visible: visibleSelector,
 | 
			
		||||
          setVisible: setVisibleSelector,
 | 
			
		||||
          modalProps: {
 | 
			
		||||
            getContainer: others?.getContainer,
 | 
			
		||||
            getContainer: others?.getContainer || modalProps?.getContainer,
 | 
			
		||||
          },
 | 
			
		||||
          formValueChanged: false,
 | 
			
		||||
        }}
 | 
			
		||||
 | 
			
		||||
@ -1,58 +1,61 @@
 | 
			
		||||
import { css, cx } from '@emotion/css';
 | 
			
		||||
import { FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import { RecursionField, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { RecursionField, useField, useFieldSchema, observer } from '@formily/react';
 | 
			
		||||
import React, { useEffect } from 'react';
 | 
			
		||||
import { CollectionProvider } from '../../../collection-manager';
 | 
			
		||||
import { useAssociationFieldContext, useInsertSchema } from './hooks';
 | 
			
		||||
import schema from './schema';
 | 
			
		||||
 | 
			
		||||
export const InternalNester = () => {
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const insertNester = useInsertSchema('Nester');
 | 
			
		||||
  const { options: collectionField } = useAssociationFieldContext();
 | 
			
		||||
  const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true;
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    insertNester(schema.Nester);
 | 
			
		||||
  }, []);
 | 
			
		||||
  return (
 | 
			
		||||
    <CollectionProvider name={collectionField.target}>
 | 
			
		||||
      <FormLayout layout={'vertical'}>
 | 
			
		||||
        <div
 | 
			
		||||
          className={cx(
 | 
			
		||||
            css`
 | 
			
		||||
              & .ant-formily-item-layout-vertical {
 | 
			
		||||
                margin-bottom: 10px;
 | 
			
		||||
              }
 | 
			
		||||
              .ant-card-body {
 | 
			
		||||
                padding: 15px 20px 5px;
 | 
			
		||||
              }
 | 
			
		||||
              .ant-divider-horizontal {
 | 
			
		||||
                margin: 10px 0;
 | 
			
		||||
              }
 | 
			
		||||
            `,
 | 
			
		||||
            {
 | 
			
		||||
              [css`
 | 
			
		||||
export const InternalNester = observer(
 | 
			
		||||
  () => {
 | 
			
		||||
    const field = useField();
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const insertNester = useInsertSchema('Nester');
 | 
			
		||||
    const { options: collectionField } = useAssociationFieldContext();
 | 
			
		||||
    const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true;
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      insertNester(schema.Nester);
 | 
			
		||||
    }, []);
 | 
			
		||||
    return (
 | 
			
		||||
      <CollectionProvider name={collectionField.target}>
 | 
			
		||||
        <FormLayout layout={'vertical'}>
 | 
			
		||||
          <div
 | 
			
		||||
            className={cx(
 | 
			
		||||
              css`
 | 
			
		||||
                & .ant-formily-item-layout-vertical {
 | 
			
		||||
                  margin-bottom: 10px;
 | 
			
		||||
                }
 | 
			
		||||
                .ant-card-body {
 | 
			
		||||
                  padding: 0px 20px 20px 0px;
 | 
			
		||||
                  padding: 15px 20px 5px;
 | 
			
		||||
                }
 | 
			
		||||
                > .ant-card-bordered {
 | 
			
		||||
                  border: none;
 | 
			
		||||
                .ant-divider-horizontal {
 | 
			
		||||
                  margin: 10px 0;
 | 
			
		||||
                }
 | 
			
		||||
              `]: showTitle === false,
 | 
			
		||||
            },
 | 
			
		||||
          )}
 | 
			
		||||
        >
 | 
			
		||||
          <RecursionField
 | 
			
		||||
            onlyRenderProperties
 | 
			
		||||
            basePath={field.address}
 | 
			
		||||
            schema={fieldSchema}
 | 
			
		||||
            filterProperties={(s) => {
 | 
			
		||||
              return s['x-component'] === 'AssociationField.Nester';
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </FormLayout>
 | 
			
		||||
    </CollectionProvider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
              `,
 | 
			
		||||
              {
 | 
			
		||||
                [css`
 | 
			
		||||
                  .ant-card-body {
 | 
			
		||||
                    padding: 0px 20px 20px 0px;
 | 
			
		||||
                  }
 | 
			
		||||
                  > .ant-card-bordered {
 | 
			
		||||
                    border: none;
 | 
			
		||||
                  }
 | 
			
		||||
                `]: showTitle === false,
 | 
			
		||||
              },
 | 
			
		||||
            )}
 | 
			
		||||
          >
 | 
			
		||||
            <RecursionField
 | 
			
		||||
              onlyRenderProperties
 | 
			
		||||
              basePath={field.address}
 | 
			
		||||
              schema={fieldSchema}
 | 
			
		||||
              filterProperties={(s) => {
 | 
			
		||||
                return s['x-component'] === 'AssociationField.Nester';
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </FormLayout>
 | 
			
		||||
      </CollectionProvider>
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
  { displayName: 'InternalNester' },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ -23,13 +23,9 @@ import { flatData, getLabelFormatValue, useLabelUiSchema } from './util';
 | 
			
		||||
 | 
			
		||||
const useTableSelectorProps = () => {
 | 
			
		||||
  const field: any = useField();
 | 
			
		||||
  const {
 | 
			
		||||
    multiple,
 | 
			
		||||
    options = [],
 | 
			
		||||
    setSelectedRows,
 | 
			
		||||
    selectedRows: rcSelectRows = [],
 | 
			
		||||
    onChange,
 | 
			
		||||
  } = useContext(RecordPickerContext);
 | 
			
		||||
  const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext(
 | 
			
		||||
    RecordPickerContext,
 | 
			
		||||
  );
 | 
			
		||||
  const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
 | 
			
		||||
  const { setVisible } = useActionContext();
 | 
			
		||||
  return {
 | 
			
		||||
@ -62,7 +58,7 @@ const useTableSelectorProps = () => {
 | 
			
		||||
 | 
			
		||||
export const InternalPicker = observer(
 | 
			
		||||
  (props: any) => {
 | 
			
		||||
    const { value, multiple, onChange, quickUpload, selectFile, ...others } = props;
 | 
			
		||||
    const { value, multiple, onChange, quickUpload, selectFile, shouldMountElement, ...others } = props;
 | 
			
		||||
    const field: any = useField();
 | 
			
		||||
    const fieldNames = useFieldNames(props);
 | 
			
		||||
    const [visibleSelector, setVisibleSelector] = useState(false);
 | 
			
		||||
@ -176,7 +172,13 @@ export const InternalPicker = observer(
 | 
			
		||||
            </RecordProvider>
 | 
			
		||||
          )}
 | 
			
		||||
        </Input.Group>
 | 
			
		||||
        <ActionContextProvider value={{ openMode: 'drawer', visible: visibleSelector, setVisible: setVisibleSelector }}>
 | 
			
		||||
        <ActionContextProvider
 | 
			
		||||
          value={{
 | 
			
		||||
            openMode: 'drawer',
 | 
			
		||||
            visible: visibleSelector,
 | 
			
		||||
            setVisible: setVisibleSelector,
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <RecordPickerProvider {...pickerProps}>
 | 
			
		||||
            <CollectionProvider name={collectionField?.target}>
 | 
			
		||||
              <FormProvider>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,87 @@
 | 
			
		||||
import { Popover } from 'antd';
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { EditOutlined } from '@ant-design/icons';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import React, { useContext, useRef, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { ReadPrettyInternalViewer } from './InternalViewer';
 | 
			
		||||
import { InternalNester } from './InternalNester';
 | 
			
		||||
import { useAssociationFieldContext } from './hooks';
 | 
			
		||||
import { ActionContextProvider, ActionContext } from '../action/context';
 | 
			
		||||
 | 
			
		||||
export const InternaPopoverNester = observer(
 | 
			
		||||
  (props) => {
 | 
			
		||||
    const { options } = useAssociationFieldContext();
 | 
			
		||||
    const [visible, setVisible] = useState(false);
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const ref = useRef();
 | 
			
		||||
    const nesterProps = {
 | 
			
		||||
      ...props,
 | 
			
		||||
      shouldMountElement: true,
 | 
			
		||||
    };
 | 
			
		||||
    const content = (
 | 
			
		||||
      <div
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        style={{ minWidth: '600px', maxWidth: '800px', maxHeight: '440px', overflow: 'auto' }}
 | 
			
		||||
        className={css`
 | 
			
		||||
          min-width: 600px;
 | 
			
		||||
          max-height: 440px;
 | 
			
		||||
          overflow: auto;
 | 
			
		||||
          .ant-card {
 | 
			
		||||
            border: 0px;
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
      >
 | 
			
		||||
        <InternalNester {...nesterProps} />
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
    const titleProps = {
 | 
			
		||||
      ...props,
 | 
			
		||||
      enableLink: true,
 | 
			
		||||
    };
 | 
			
		||||
    const getContainer = () => ref.current;
 | 
			
		||||
    const ctx = useContext(ActionContext);
 | 
			
		||||
    const modalProps = {
 | 
			
		||||
      getContainer: getContainer,
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
      <ActionContextProvider value={{ ...ctx, modalProps }}>
 | 
			
		||||
        <Popover
 | 
			
		||||
          overlayStyle={{ padding: '0px' }}
 | 
			
		||||
          content={content}
 | 
			
		||||
          trigger="click"
 | 
			
		||||
          placement="topLeft"
 | 
			
		||||
          open={visible}
 | 
			
		||||
          onOpenChange={(open) => setVisible(open)}
 | 
			
		||||
          title={t(options?.uiSchema?.rawTitle)}
 | 
			
		||||
        >
 | 
			
		||||
          <span style={{ cursor: 'pointer', display: 'flex' }}>
 | 
			
		||||
            <div
 | 
			
		||||
              className={css`
 | 
			
		||||
                max-width: 95%;
 | 
			
		||||
              `}
 | 
			
		||||
            >
 | 
			
		||||
              <ReadPrettyInternalViewer {...titleProps} />
 | 
			
		||||
            </div>
 | 
			
		||||
            <EditOutlined style={{ display: 'inline-flex', marginLeft: '5px' }} />
 | 
			
		||||
          </span>
 | 
			
		||||
        </Popover>
 | 
			
		||||
        {visible && (
 | 
			
		||||
          <div
 | 
			
		||||
            onClick={() => setVisible(false)}
 | 
			
		||||
            className={css`
 | 
			
		||||
              position: fixed;
 | 
			
		||||
              top: 0;
 | 
			
		||||
              left: 0;
 | 
			
		||||
              width: 100%;
 | 
			
		||||
              height: 100%;
 | 
			
		||||
              background-color: transparent;
 | 
			
		||||
              z-index: 9999;
 | 
			
		||||
            `}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
      </ActionContextProvider>
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
  { displayName: 'InternaPopoverNester' },
 | 
			
		||||
);
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { FormItem, FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import { FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import { RecursionField, observer, useField, useFieldSchema, SchemaOptionsContext } from '@formily/react';
 | 
			
		||||
import React, { useEffect } from 'react';
 | 
			
		||||
import { CollectionProvider } from '../../../collection-manager';
 | 
			
		||||
@ -22,7 +22,6 @@ export const InternalSubTable = observer(
 | 
			
		||||
    const option = useSchemaOptionsContext();
 | 
			
		||||
    const components = {
 | 
			
		||||
      ...option.components,
 | 
			
		||||
      FormItem: (props) => <FormItem {...props} />,
 | 
			
		||||
      'Radio.Group': Select,
 | 
			
		||||
      'Checkbox.Group': (props) => <Select multiple={true} mode="multiple" {...props} />,
 | 
			
		||||
    };
 | 
			
		||||
@ -38,9 +37,8 @@ export const InternalSubTable = observer(
 | 
			
		||||
              .ant-checkbox-wrapper {
 | 
			
		||||
                margin-left: 8px;
 | 
			
		||||
              }
 | 
			
		||||
              .ant-btn {
 | 
			
		||||
                border: none !important;
 | 
			
		||||
                box-shadow: none;
 | 
			
		||||
              .ant-table {
 | 
			
		||||
                margin: 0px !important;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          `}
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,6 @@ export const ReadPrettyInternalViewer: React.FC = observer(
 | 
			
		||||
    const { designable } = useDesignable();
 | 
			
		||||
    const { snapshot } = useActionContext();
 | 
			
		||||
    const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
 | 
			
		||||
 | 
			
		||||
    const renderRecords = () =>
 | 
			
		||||
      toArr(props.value).map((record, index, arr) => {
 | 
			
		||||
        const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A');
 | 
			
		||||
@ -53,7 +52,7 @@ export const ReadPrettyInternalViewer: React.FC = observer(
 | 
			
		||||
            <span>
 | 
			
		||||
              {snapshot ? (
 | 
			
		||||
                text
 | 
			
		||||
              ) : enableLink !== false ? (
 | 
			
		||||
              ) : enableLink !== false && !props.enableLink ? (
 | 
			
		||||
                <a
 | 
			
		||||
                  onClick={(e) => {
 | 
			
		||||
                    e.stopPropagation();
 | 
			
		||||
@ -76,7 +75,6 @@ export const ReadPrettyInternalViewer: React.FC = observer(
 | 
			
		||||
          </Fragment>
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    const renderWithoutTableFieldResourceProvider = () => (
 | 
			
		||||
      <WithoutTableFieldResource.Provider value={true}>
 | 
			
		||||
        <FormProvider>
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ const ToOneNester = (props) => {
 | 
			
		||||
 | 
			
		||||
const ToManyNester = observer(
 | 
			
		||||
  (props) => {
 | 
			
		||||
    console.log(props);
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>();
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
 | 
			
		||||
import { useCompile, useDesignable } from '../../hooks';
 | 
			
		||||
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
 | 
			
		||||
import { useAssociationFieldContext } from '../association-field/hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
 | 
			
		||||
@ -62,6 +62,10 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(collectionField?.target);
 | 
			
		||||
  const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
 | 
			
		||||
  const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o', 'snapshot'].includes(
 | 
			
		||||
    collectionField?.interface,
 | 
			
		||||
  );
 | 
			
		||||
  const fieldModeOptions = useFieldModeOptions({ fieldSchema });
 | 
			
		||||
  const fieldMode = fieldSchema?.['x-component-props']?.['mode'] || 'Select';
 | 
			
		||||
  let readOnlyMode = 'editable';
 | 
			
		||||
  if (fieldSchema['x-disabled'] === true) {
 | 
			
		||||
@ -250,32 +254,35 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {readOnlyMode === 'read-pretty' &&
 | 
			
		||||
        ['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho', 'snapshot'].includes(collectionField?.interface) && (
 | 
			
		||||
          <SchemaSettings.SelectItem
 | 
			
		||||
            key="field-mode"
 | 
			
		||||
            title={t('Field component')}
 | 
			
		||||
            options={[
 | 
			
		||||
              { label: t('Title'), value: 'Select' },
 | 
			
		||||
              { label: t('Tag'), value: 'Tag' },
 | 
			
		||||
            ]}
 | 
			
		||||
            value={fieldMode}
 | 
			
		||||
            onChange={(mode) => {
 | 
			
		||||
              const schema = {
 | 
			
		||||
                ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
              };
 | 
			
		||||
              fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
 | 
			
		||||
              fieldSchema['x-component-props']['mode'] = mode;
 | 
			
		||||
              schema['x-component-props'] = fieldSchema['x-component-props'];
 | 
			
		||||
              field.componentProps = field.componentProps || {};
 | 
			
		||||
              field.componentProps.mode = mode;
 | 
			
		||||
              dn.emit('patch', {
 | 
			
		||||
                schema,
 | 
			
		||||
              });
 | 
			
		||||
              dn.refresh();
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
      {isAssociationField && (
 | 
			
		||||
        <SchemaSettings.SelectItem
 | 
			
		||||
          key="field-mode"
 | 
			
		||||
          title={t('Field component')}
 | 
			
		||||
          options={
 | 
			
		||||
            readOnlyMode === 'read-pretty'
 | 
			
		||||
              ? [
 | 
			
		||||
                  { label: t('Title'), value: 'Select' },
 | 
			
		||||
                  { label: t('Tag'), value: 'Tag' },
 | 
			
		||||
                ]
 | 
			
		||||
              : fieldModeOptions
 | 
			
		||||
          }
 | 
			
		||||
          value={fieldMode}
 | 
			
		||||
          onChange={(mode) => {
 | 
			
		||||
            const schema = {
 | 
			
		||||
              ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            };
 | 
			
		||||
            fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
 | 
			
		||||
            fieldSchema['x-component-props']['mode'] = mode;
 | 
			
		||||
            schema['x-component-props'] = fieldSchema['x-component-props'];
 | 
			
		||||
            field.componentProps = field.componentProps || {};
 | 
			
		||||
            field.componentProps.mode = mode;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
              schema,
 | 
			
		||||
            });
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {['Tag'].includes(fieldMode) && (
 | 
			
		||||
        <SchemaSettings.SelectItem
 | 
			
		||||
@ -299,7 +306,6 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {isSubTableColumn && !field.readPretty && !uiSchema?.['x-read-pretty'] && (
 | 
			
		||||
        <SchemaSettings.SwitchItem
 | 
			
		||||
          key="required"
 | 
			
		||||
 | 
			
		||||
@ -3,11 +3,13 @@ import { useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../collection-manager';
 | 
			
		||||
 | 
			
		||||
export const useFieldModeOptions = () => {
 | 
			
		||||
export const useFieldModeOptions = (props?) => {
 | 
			
		||||
  const { getCollectionJoinField, getCollection } = useCollectionManager();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const currentFieldSchema = useFieldSchema();
 | 
			
		||||
  const fieldSchema = props?.fieldSchema || currentFieldSchema;
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const isReadPretty = field.readPretty;
 | 
			
		||||
  const isSubTableField = props?.fieldSchema;
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
  const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
@ -16,7 +18,6 @@ export const useFieldModeOptions = () => {
 | 
			
		||||
    if (!collectionField || !collectionField?.interface) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!['o2o', 'oho', 'obo', 'o2m', 'linkTo', 'm2o', 'm2m'].includes(collectionField.interface)) return;
 | 
			
		||||
    const collection = getCollection(collectionField.target);
 | 
			
		||||
    if (collection?.template === 'file') {
 | 
			
		||||
@ -29,10 +30,9 @@ export const useFieldModeOptions = () => {
 | 
			
		||||
        : [
 | 
			
		||||
            { label: t('Select'), value: 'Select' },
 | 
			
		||||
            { label: t('Record picker'), value: 'Picker' },
 | 
			
		||||
            { label: t('File manager'), value: 'FileManager' },
 | 
			
		||||
            !isSubTableField && { label: t('File manager'), value: 'FileManager' },
 | 
			
		||||
          ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (collectionField.interface) {
 | 
			
		||||
      case 'o2m':
 | 
			
		||||
        return isReadPretty
 | 
			
		||||
@ -45,8 +45,9 @@ export const useFieldModeOptions = () => {
 | 
			
		||||
          : [
 | 
			
		||||
              { label: t('Select'), value: 'Select' },
 | 
			
		||||
              { label: t('Record picker'), value: 'Picker' },
 | 
			
		||||
              { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              { label: t('Sub-table'), value: 'SubTable' },
 | 
			
		||||
              !isSubTableField && { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              { label: t('Sub-form(Popover)'), value: 'PopoverNester' },
 | 
			
		||||
              !isSubTableField && { label: t('Sub-table'), value: 'SubTable' },
 | 
			
		||||
            ];
 | 
			
		||||
      case 'm2m':
 | 
			
		||||
        return isReadPretty
 | 
			
		||||
@ -59,8 +60,9 @@ export const useFieldModeOptions = () => {
 | 
			
		||||
          : [
 | 
			
		||||
              { label: t('Select'), value: 'Select' },
 | 
			
		||||
              { label: t('Record picker'), value: 'Picker' },
 | 
			
		||||
              { label: t('Sub-table'), value: 'SubTable' },
 | 
			
		||||
              { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              !isSubTableField && { label: t('Sub-table'), value: 'SubTable' },
 | 
			
		||||
              !isSubTableField && { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              { label: t('Sub-form(Popover)'), value: 'PopoverNester' },
 | 
			
		||||
            ];
 | 
			
		||||
      case 'm2o':
 | 
			
		||||
      case 'linkTo':
 | 
			
		||||
@ -73,7 +75,8 @@ export const useFieldModeOptions = () => {
 | 
			
		||||
          : [
 | 
			
		||||
              { label: t('Select'), value: 'Select' },
 | 
			
		||||
              { label: t('Record picker'), value: 'Picker' },
 | 
			
		||||
              { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              !isSubTableField && { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              { label: t('Sub-form(Popover)'), value: 'PopoverNester' },
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
@ -86,9 +89,10 @@ export const useFieldModeOptions = () => {
 | 
			
		||||
          : [
 | 
			
		||||
              { label: t('Select'), value: 'Select' },
 | 
			
		||||
              { label: t('Record picker'), value: 'Picker' },
 | 
			
		||||
              { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              !isSubTableField && { label: t('Sub-form'), value: 'Nester' },
 | 
			
		||||
              { label: t('Sub-form(Popover)'), value: 'PopoverNester' },
 | 
			
		||||
            ];
 | 
			
		||||
    }
 | 
			
		||||
  }, [t, collectionField?.interface, label]);
 | 
			
		||||
  return fieldModeOptions;
 | 
			
		||||
  return (fieldModeOptions || []).filter(Boolean);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -290,6 +290,17 @@ export class Database extends EventEmitter implements AsyncEmitter {
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.migrator = new Umzug({
 | 
			
		||||
      logger: migratorOptions.logger || console,
 | 
			
		||||
      migrations: this.migrations.callback(),
 | 
			
		||||
      context,
 | 
			
		||||
      storage: new SequelizeStorage({
 | 
			
		||||
        modelName: `${this.options.tablePrefix || ''}migrations`,
 | 
			
		||||
        ...migratorOptions.storage,
 | 
			
		||||
        sequelize: this.sequelize,
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.initListener();
 | 
			
		||||
    patchSequelizeQueryInterface(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user