feat(variable): lazy load association fields (#2382)
* refactor: remove useless code * feat: should always show user variable * style: optimize style of variable input * feat: change maxDepth of user varialbe to 3 * refactor: migrate getFieldDefaultValue to SchemaSettings.tsx * feat: support to parse variables * feat: support to parse array fields * refactor: make testing more stable * chore: fix CI * feat: support to parse current user * fix: fix tests * feat: support to register variable * feat: support to parse "$context" * refactor: remove useless code * fix: fix build * fix: fix build * test: make stable * docs: add doc * fix: fix T-1267 * fix: fix T-1268 * fix: fix T-1269 * fix: fix T-1271 * fix: fix T-1274 * refactor: improve code * feat(variable): support local variable * fix: fix T-1280 * refactor: close T-881 * refactor: remove useLazyLoadAssociation * fix: fix T-1289,T-1290 * fix: fix T-1296 * refactor(AssignedField): replace user variable * refactor: extract useBaseVariable * refactor(AssignedField): replace variables * feat(useVariables): support to use array as local variables * refactor: improve variables (T-1295) * feat: limit user choice (T-1297) * fix: fix T-1305 * refactor: extract as SchemaSettings.DataScope * refactor: improve linkage rules * refactor: improve linkage rules of Action * chore: fix build * refactor: improve data scope of association fields * chore: fix build * feat: select limit * refactor: use VariableInput to replace other * fix: fix can not input * fix(AssignedField): fix cannot close variables list * test: fix errors * refactor: migrate to new file * fix: uniq * fix: filter empty values * fix: transform variables value * fix: should sum numbers (T-1330) * fix: fix Select (T-1337) * refactor: use useMemo * refactor: use hooks * refactor: use hooks * refactor: use hooks * refactor: remove useless code * refactor: rename `$form` to `$nForm` and `$record` to `$nRecord` * fix: fix T-1334 * refactor(LinkageRuleActionGroup): use hooks * refactor(FormButtonLinkageRuleAction): use hooks * refactor: add type * fix: avoid crashing * refactor: rename `$date` to `$nDate` * fix: fix T-1348 * perf: avoid duplicate requests * test: fix error * fix: fix T-1355 * fix: fix T-1355 * fix: fix T-1345 * fix: fix T-1346 * refactor(useVariableOptions): replace query with parameter * fix: fix T-1335 * chore: fix build * fix: fix T-1453 * refactor: remove useless code * fix: fix T-1447 * fix: should update when seting default value * fix: fix T-1454 * chore(eslint): ignore docker * fix: fix T-1440 * refactor: use hooks * fix: fix T-1332 * fix(defaultValue): should use Select mode * fix(defaultValue): fix Checkbox * refactor: rename and comment * refactor: rename file name and hook name * feat(subForm): support lazy load association fields * fix: filter empty values * fix(defaultValue): schema type of Checkbox should be void * fix: fix T-1479 * fix: fix T-1491 * fix(ToOneNester): should use RecordProvider * fix: fix T-1493 * chore: add comment * fix: fix T-1495 * chore: fix build * test: make pass * fix: fix T-1503 * fix: fix T-1504 * fix: fix transformVariableValue * fix: disable unsupport options * refactor(useVariableOptions): add noDisabled params * fix: data scope support to select to-many fields * chore: add comment * refactor: improve default value * refactor: extract as useIsAllowToSetDefaultValue * fix: should not support to set default value in edit mode * fix: only the default values for sub-forms and sub-tables are valid * fix: limit default value of association fields * fix: fix '$nForm' variable * fix: allow all inherit chain * fix: optimize default value * fix: optimize json field * fix(currentObject): avoid error * fix: should not show variable string * fix: active fields * fix: avoid errors * feat: support special case * fix: fix CI * chore: fix build * fix: json * refactor: rename flag name * fix: fix T-1559 * fix: fix T-1515 * fix: avoid crash * fix: fix T-1520 * fix: fix T-1521 * fix: fix T-1523 * fix: fix T-1563 * fix: fix T-1565 * fix: fix T-1525 * fix: fix T-1524 * fix: fix T-1603 * fix: fix T-1332 * fix: fix T-1559 * fix: fix T-1602 * fix: fix T-1608 * fix: fix T-1612 T-1609 * fix: fix T-1681 * fix: fix T-1711 * fix: fix T-1713 * fix: fix T-1680 * chore: close T-1550 * fix: fix T-1507 * chore: avoid crashing * fix: fix T-1557 * perf: close T-1726 * fix: fix T-1727 * fix: fix T-1741 * fix: fix T-1740 * chore(workflow): compact * chore: should not to support to set default value in assign field values * perf: compat and perf (T-1736) * fix: fix T-1872 * fix: fix T-1877 * fix: fix quarter variables * chore: add README * chore: front-end should not depend on back-end code * chore: should disable all variable options if no field is selected * chore: should be a leaf if it has no children * chore: fix build error * fix: compat $form * fix: compat $form * chore: fix build * chore: fix build * fix: typeError: Cannot read properties of undefined (reading 'template') * chore: fix test * chore: add test * fix: fix T-2072 * chore: optimize eslint * perf: reduce number of requests * fix: fix JSON * test: fix error * fix(e2e): avoid error --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
		
							parent
							
								
									c4f572fda8
								
							
						
					
					
						commit
						573cde0a21
					
				
							
								
								
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@ -30,7 +30,7 @@
 | 
			
		||||
      "name": "Debug Client Tests",
 | 
			
		||||
      "runtimeExecutable": "yarn",
 | 
			
		||||
      "runtimeArgs": ["run", "test:client", "${file}"],
 | 
			
		||||
      "skipFiles": ["<node_internals>/**"],
 | 
			
		||||
      "skipFiles": ["<node_internals>/**", "**/node_modules/**", "**/dist/**", "**/lib/**", "**/es/**"],
 | 
			
		||||
      "console": "integratedTerminal",
 | 
			
		||||
      "internalConsoleOptions": "neverOpen",
 | 
			
		||||
      "windows": {
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,13 @@
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import react from '@vitejs/plugin-react';
 | 
			
		||||
import fg from 'fast-glob';
 | 
			
		||||
import fs from 'fs-extra';
 | 
			
		||||
import { build as viteBuild } from 'vite';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import { build as tsupBuild } from 'tsup';
 | 
			
		||||
import { build as viteBuild } from 'vite';
 | 
			
		||||
import { libInjectCss } from 'vite-plugin-lib-inject-css';
 | 
			
		||||
import react from '@vitejs/plugin-react';
 | 
			
		||||
 | 
			
		||||
import { PkgLog, UserConfig } from './utils';
 | 
			
		||||
import { globExcludeFiles } from './constant';
 | 
			
		||||
 | 
			
		||||
import { PkgLog, UserConfig } from './utils';
 | 
			
		||||
 | 
			
		||||
export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap: boolean = false, log: PkgLog) {
 | 
			
		||||
  log('build client');
 | 
			
		||||
@ -19,7 +18,7 @@ export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  };
 | 
			
		||||
  await buildEsm(cwd, userConfig, sourcemap, external, log);
 | 
			
		||||
  await buildLib(cwd, userConfig, sourcemap, external, log);
 | 
			
		||||
  await buildLocale(cwd, userConfig, log);
 | 
			
		||||
@ -31,10 +30,12 @@ export function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean
 | 
			
		||||
  log('build client esm');
 | 
			
		||||
  const entry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/');
 | 
			
		||||
  const outDir = path.resolve(cwd, 'es');
 | 
			
		||||
  return viteBuild(userConfig.modifyViteConfig({
 | 
			
		||||
  return viteBuild(
 | 
			
		||||
    userConfig.modifyViteConfig({
 | 
			
		||||
      mode: 'production',
 | 
			
		||||
      define: {
 | 
			
		||||
        'process.env.NODE_ENV': JSON.stringify('production'),
 | 
			
		||||
        'process.env.__TEST__': false,
 | 
			
		||||
      },
 | 
			
		||||
      build: {
 | 
			
		||||
        minify: false,
 | 
			
		||||
@ -55,22 +56,30 @@ export function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      plugins: [react(), libInjectCss()],
 | 
			
		||||
  }));
 | 
			
		||||
    }),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function buildLib(cwd: string, userConfig: UserConfig, sourcemap: boolean, external: External, log: PkgLog) {
 | 
			
		||||
export async function buildLib(
 | 
			
		||||
  cwd: string,
 | 
			
		||||
  userConfig: UserConfig,
 | 
			
		||||
  sourcemap: boolean,
 | 
			
		||||
  external: External,
 | 
			
		||||
  log: PkgLog,
 | 
			
		||||
) {
 | 
			
		||||
  log('build client lib');
 | 
			
		||||
  const outDir = path.resolve(cwd, 'lib');
 | 
			
		||||
  const esDir = path.resolve(cwd, 'es');
 | 
			
		||||
  const entry = path.join(esDir, 'index.ts')
 | 
			
		||||
  const entry = path.join(esDir, 'index.ts');
 | 
			
		||||
 | 
			
		||||
  fs.removeSync(entry);
 | 
			
		||||
  fs.linkSync(path.join(cwd, 'es/index.mjs'), entry);
 | 
			
		||||
 | 
			
		||||
  await viteBuild(userConfig.modifyViteConfig({
 | 
			
		||||
  await viteBuild(
 | 
			
		||||
    userConfig.modifyViteConfig({
 | 
			
		||||
      mode: 'production',
 | 
			
		||||
      esbuild: {
 | 
			
		||||
      format: 'cjs'
 | 
			
		||||
        format: 'cjs',
 | 
			
		||||
      },
 | 
			
		||||
      build: {
 | 
			
		||||
        outDir,
 | 
			
		||||
@ -85,7 +94,8 @@ export async function buildLib(cwd: string, userConfig: UserConfig, sourcemap: b
 | 
			
		||||
          external,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
  }));
 | 
			
		||||
    }),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  fs.removeSync(entry);
 | 
			
		||||
 | 
			
		||||
@ -100,7 +110,8 @@ export function buildLocale(cwd: string, userConfig: UserConfig, log: PkgLog) {
 | 
			
		||||
 | 
			
		||||
  const entry = fg.globSync(['src/locale/**', ...globExcludeFiles], { cwd, absolute: true });
 | 
			
		||||
  const outDir = path.resolve(cwd, 'lib', 'locale');
 | 
			
		||||
  return tsupBuild(userConfig.modifyTsupConfig({
 | 
			
		||||
  return tsupBuild(
 | 
			
		||||
    userConfig.modifyTsupConfig({
 | 
			
		||||
      entry,
 | 
			
		||||
      splitting: false,
 | 
			
		||||
      clean: false,
 | 
			
		||||
@ -112,5 +123,6 @@ export function buildLocale(cwd: string, userConfig: UserConfig, log: PkgLog) {
 | 
			
		||||
      outDir,
 | 
			
		||||
      format: 'cjs',
 | 
			
		||||
      skipNodeModulesBundle: true,
 | 
			
		||||
  }));
 | 
			
		||||
    }),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -303,6 +303,7 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou
 | 
			
		||||
    mode: 'production',
 | 
			
		||||
    define: {
 | 
			
		||||
      'process.env.NODE_ENV': JSON.stringify('production'),
 | 
			
		||||
      'process.env.__TEST__': false,
 | 
			
		||||
    },
 | 
			
		||||
    logLevel: 'warn',
 | 
			
		||||
    build: {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										803
									
								
								packages/core/client/src/__tests__/e2e/variables.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										803
									
								
								packages/core/client/src/__tests__/e2e/variables.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,803 @@
 | 
			
		||||
import { test } from '@nocobase/test/client';
 | 
			
		||||
 | 
			
		||||
const t2072Config = {
 | 
			
		||||
  collections: [
 | 
			
		||||
    {
 | 
			
		||||
      key: 'etlrv6xojl4',
 | 
			
		||||
      name: 't_cyjvg3xxak7',
 | 
			
		||||
      title: '人工>组织[普通表]',
 | 
			
		||||
      inherit: false,
 | 
			
		||||
      hidden: false,
 | 
			
		||||
      description: null,
 | 
			
		||||
      fields: [
 | 
			
		||||
        {
 | 
			
		||||
          key: 'e4jfv35cotm',
 | 
			
		||||
          name: 'id',
 | 
			
		||||
          type: 'bigInt',
 | 
			
		||||
          interface: 'id',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          autoIncrement: true,
 | 
			
		||||
          primaryKey: true,
 | 
			
		||||
          allowNull: false,
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
            title: '{{t("ID")}}',
 | 
			
		||||
            'x-component': 'InputNumber',
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: '778zti7timg',
 | 
			
		||||
          name: 'f_jnf1x0qxsnz',
 | 
			
		||||
          type: 'bigInt',
 | 
			
		||||
          interface: 'integer',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          isForeignKey: true,
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
            title: 'f_jnf1x0qxsnz',
 | 
			
		||||
            'x-component': 'InputNumber',
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: '2nuvtsxlzvh',
 | 
			
		||||
          name: 'f_zx8j9897cv5',
 | 
			
		||||
          type: 'bigInt',
 | 
			
		||||
          interface: 'integer',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          isForeignKey: true,
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
            title: 'f_zx8j9897cv5',
 | 
			
		||||
            'x-component': 'InputNumber',
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: 'q491jgqygpg',
 | 
			
		||||
          name: 'createdAt',
 | 
			
		||||
          type: 'date',
 | 
			
		||||
          interface: 'createdAt',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          field: 'createdAt',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'datetime',
 | 
			
		||||
            title: '{{t("Created at")}}',
 | 
			
		||||
            'x-component': 'DatePicker',
 | 
			
		||||
            'x-component-props': {},
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: 'q2uungrabre',
 | 
			
		||||
          name: 'createdBy',
 | 
			
		||||
          type: 'belongsTo',
 | 
			
		||||
          interface: 'createdBy',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          target: 'users',
 | 
			
		||||
          foreignKey: 'createdById',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: '{{t("Created by")}}',
 | 
			
		||||
            'x-component': 'AssociationField',
 | 
			
		||||
            'x-component-props': {
 | 
			
		||||
              fieldNames: {
 | 
			
		||||
                value: 'id',
 | 
			
		||||
                label: 'nickname',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: 'cizz53lon1q',
 | 
			
		||||
          name: 'updatedAt',
 | 
			
		||||
          type: 'date',
 | 
			
		||||
          interface: 'updatedAt',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          field: 'updatedAt',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            title: '{{t("Last updated at")}}',
 | 
			
		||||
            'x-component': 'DatePicker',
 | 
			
		||||
            'x-component-props': {},
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: '9dd8bohloh2',
 | 
			
		||||
          name: 'updatedBy',
 | 
			
		||||
          type: 'belongsTo',
 | 
			
		||||
          interface: 'updatedBy',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          target: 'users',
 | 
			
		||||
          foreignKey: 'updatedById',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: '{{t("Last updated by")}}',
 | 
			
		||||
            'x-component': 'AssociationField',
 | 
			
		||||
            'x-component-props': {
 | 
			
		||||
              fieldNames: {
 | 
			
		||||
                value: 'id',
 | 
			
		||||
                label: 'nickname',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: 'p7649gl5tic',
 | 
			
		||||
          name: 'f_wrlootg9xu9',
 | 
			
		||||
          type: 'hasMany',
 | 
			
		||||
          interface: 'o2m',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          foreignKey: 'f_jnf1x0qxsnz',
 | 
			
		||||
          onDelete: 'SET NULL',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            'x-component': 'AssociationField',
 | 
			
		||||
            'x-component-props': {
 | 
			
		||||
              multiple: true,
 | 
			
		||||
              fieldNames: {
 | 
			
		||||
                label: 'id',
 | 
			
		||||
                value: 'id',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            title: '部门(one to many)',
 | 
			
		||||
          },
 | 
			
		||||
          target: 't_cyjvg3xxak7',
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
          sourceKey: 'id',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: '6etvnvsplrv',
 | 
			
		||||
          name: 'f_4xulfnosvlx',
 | 
			
		||||
          type: 'belongsTo',
 | 
			
		||||
          interface: 'm2o',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_cyjvg3xxak7',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          foreignKey: 'f_zx8j9897cv5',
 | 
			
		||||
          onDelete: 'SET NULL',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            'x-component': 'AssociationField',
 | 
			
		||||
            'x-component-props': {
 | 
			
		||||
              multiple: false,
 | 
			
		||||
              fieldNames: {
 | 
			
		||||
                label: 'id',
 | 
			
		||||
                value: 'id',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            title: '负责人(many to one)',
 | 
			
		||||
          },
 | 
			
		||||
          target: 't_ymx99ljfetl',
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      category: [],
 | 
			
		||||
      logging: true,
 | 
			
		||||
      autoGenId: true,
 | 
			
		||||
      createdBy: true,
 | 
			
		||||
      updatedBy: true,
 | 
			
		||||
      createdAt: true,
 | 
			
		||||
      updatedAt: true,
 | 
			
		||||
      sortable: true,
 | 
			
		||||
      template: 'general',
 | 
			
		||||
      view: false,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      key: 'gmsav2kkbk3',
 | 
			
		||||
      name: 't_ymx99ljfetl',
 | 
			
		||||
      title: '人员',
 | 
			
		||||
      inherit: false,
 | 
			
		||||
      hidden: false,
 | 
			
		||||
      description: null,
 | 
			
		||||
      fields: [
 | 
			
		||||
        {
 | 
			
		||||
          key: 'dhonulr1xxc',
 | 
			
		||||
          name: 'id',
 | 
			
		||||
          type: 'bigInt',
 | 
			
		||||
          interface: 'id',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_ymx99ljfetl',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          autoIncrement: true,
 | 
			
		||||
          primaryKey: true,
 | 
			
		||||
          allowNull: false,
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
            title: '{{t("ID")}}',
 | 
			
		||||
            'x-component': 'InputNumber',
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: 'fh09uvcibsq',
 | 
			
		||||
          name: 'f_qojsgjsrjie',
 | 
			
		||||
          type: 'bigInt',
 | 
			
		||||
          interface: 'integer',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_ymx99ljfetl',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          isForeignKey: true,
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'number',
 | 
			
		||||
            title: 'f_qojsgjsrjie',
 | 
			
		||||
            'x-component': 'InputNumber',
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: 'xzvwq5g75gi',
 | 
			
		||||
          name: 'createdAt',
 | 
			
		||||
          type: 'date',
 | 
			
		||||
          interface: 'createdAt',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_ymx99ljfetl',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          field: 'createdAt',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'datetime',
 | 
			
		||||
            title: '{{t("Created at")}}',
 | 
			
		||||
            'x-component': 'DatePicker',
 | 
			
		||||
            'x-component-props': {},
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: 'oc580r95evm',
 | 
			
		||||
          name: 'createdBy',
 | 
			
		||||
          type: 'belongsTo',
 | 
			
		||||
          interface: 'createdBy',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_ymx99ljfetl',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          target: 'users',
 | 
			
		||||
          foreignKey: 'createdById',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: '{{t("Created by")}}',
 | 
			
		||||
            'x-component': 'AssociationField',
 | 
			
		||||
            'x-component-props': {
 | 
			
		||||
              fieldNames: {
 | 
			
		||||
                value: 'id',
 | 
			
		||||
                label: 'nickname',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: '9zi17bh97yl',
 | 
			
		||||
          name: 'updatedAt',
 | 
			
		||||
          type: 'date',
 | 
			
		||||
          interface: 'updatedAt',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_ymx99ljfetl',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          field: 'updatedAt',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'string',
 | 
			
		||||
            title: '{{t("Last updated at")}}',
 | 
			
		||||
            'x-component': 'DatePicker',
 | 
			
		||||
            'x-component-props': {},
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: '2xbcmman6gz',
 | 
			
		||||
          name: 'updatedBy',
 | 
			
		||||
          type: 'belongsTo',
 | 
			
		||||
          interface: 'updatedBy',
 | 
			
		||||
          description: null,
 | 
			
		||||
          collectionName: 't_ymx99ljfetl',
 | 
			
		||||
          parentKey: null,
 | 
			
		||||
          reverseKey: null,
 | 
			
		||||
          target: 'users',
 | 
			
		||||
          foreignKey: 'updatedById',
 | 
			
		||||
          uiSchema: {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: '{{t("Last updated by")}}',
 | 
			
		||||
            'x-component': 'AssociationField',
 | 
			
		||||
            'x-component-props': {
 | 
			
		||||
              fieldNames: {
 | 
			
		||||
                value: 'id',
 | 
			
		||||
                label: 'nickname',
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            'x-read-pretty': true,
 | 
			
		||||
          },
 | 
			
		||||
          targetKey: 'id',
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      category: [],
 | 
			
		||||
      logging: true,
 | 
			
		||||
      autoGenId: true,
 | 
			
		||||
      createdBy: true,
 | 
			
		||||
      updatedBy: true,
 | 
			
		||||
      createdAt: true,
 | 
			
		||||
      updatedAt: true,
 | 
			
		||||
      sortable: true,
 | 
			
		||||
      template: 'general',
 | 
			
		||||
      view: false,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  pageSchema: {
 | 
			
		||||
    _isJSONSchemaObject: true,
 | 
			
		||||
    version: '2.0',
 | 
			
		||||
    type: 'void',
 | 
			
		||||
    'x-component': 'Page',
 | 
			
		||||
    properties: {
 | 
			
		||||
      '3fsvxxy8klh': {
 | 
			
		||||
        _isJSONSchemaObject: true,
 | 
			
		||||
        version: '2.0',
 | 
			
		||||
        type: 'void',
 | 
			
		||||
        'x-component': 'Grid',
 | 
			
		||||
        'x-initializer': 'BlockInitializers',
 | 
			
		||||
        properties: {
 | 
			
		||||
          kwm0euv1w2z: {
 | 
			
		||||
            _isJSONSchemaObject: true,
 | 
			
		||||
            version: '2.0',
 | 
			
		||||
            type: 'void',
 | 
			
		||||
            'x-component': 'Grid.Row',
 | 
			
		||||
            properties: {
 | 
			
		||||
              '4wk59ijgdzx': {
 | 
			
		||||
                _isJSONSchemaObject: true,
 | 
			
		||||
                version: '2.0',
 | 
			
		||||
                type: 'void',
 | 
			
		||||
                'x-component': 'Grid.Col',
 | 
			
		||||
                properties: {
 | 
			
		||||
                  vsgx66mbgpm: {
 | 
			
		||||
                    _isJSONSchemaObject: true,
 | 
			
		||||
                    version: '2.0',
 | 
			
		||||
                    type: 'void',
 | 
			
		||||
                    'x-decorator': 'TableBlockProvider',
 | 
			
		||||
                    'x-acl-action': 't_cyjvg3xxak7:list',
 | 
			
		||||
                    'x-decorator-props': {
 | 
			
		||||
                      collection: 't_cyjvg3xxak7',
 | 
			
		||||
                      resource: 't_cyjvg3xxak7',
 | 
			
		||||
                      action: 'list',
 | 
			
		||||
                      params: {
 | 
			
		||||
                        pageSize: 20,
 | 
			
		||||
                      },
 | 
			
		||||
                      rowKey: 'id',
 | 
			
		||||
                      showIndex: true,
 | 
			
		||||
                      dragSort: false,
 | 
			
		||||
                      disableTemplate: false,
 | 
			
		||||
                    },
 | 
			
		||||
                    'x-designer': 'TableBlockDesigner',
 | 
			
		||||
                    'x-component': 'CardItem',
 | 
			
		||||
                    'x-filter-targets': [],
 | 
			
		||||
                    properties: {
 | 
			
		||||
                      actions: {
 | 
			
		||||
                        _isJSONSchemaObject: true,
 | 
			
		||||
                        version: '2.0',
 | 
			
		||||
                        type: 'void',
 | 
			
		||||
                        'x-initializer': 'TableActionInitializers',
 | 
			
		||||
                        'x-component': 'ActionBar',
 | 
			
		||||
                        'x-component-props': {
 | 
			
		||||
                          style: {
 | 
			
		||||
                            marginBottom: 'var(--nb-spacing)',
 | 
			
		||||
                          },
 | 
			
		||||
                        },
 | 
			
		||||
                        properties: {
 | 
			
		||||
                          mb64tcykcw3: {
 | 
			
		||||
                            _isJSONSchemaObject: true,
 | 
			
		||||
                            version: '2.0',
 | 
			
		||||
                            type: 'void',
 | 
			
		||||
                            title: '{{t("Add record")}}',
 | 
			
		||||
                            'x-designer': 'Action.Designer',
 | 
			
		||||
                            'x-component': 'Action',
 | 
			
		||||
                            'x-action': 'customize:create',
 | 
			
		||||
                            'x-component-props': {
 | 
			
		||||
                              openMode: 'drawer',
 | 
			
		||||
                              icon: 'PlusOutlined',
 | 
			
		||||
                            },
 | 
			
		||||
                            'x-align': 'right',
 | 
			
		||||
                            'x-decorator': 'ACLActionProvider',
 | 
			
		||||
                            'x-acl-action': 'create',
 | 
			
		||||
                            'x-acl-action-props': {
 | 
			
		||||
                              skipScopeCheck: true,
 | 
			
		||||
                            },
 | 
			
		||||
                            properties: {
 | 
			
		||||
                              drawer: {
 | 
			
		||||
                                _isJSONSchemaObject: true,
 | 
			
		||||
                                version: '2.0',
 | 
			
		||||
                                type: 'void',
 | 
			
		||||
                                title: '{{t("Add record")}}',
 | 
			
		||||
                                'x-component': 'Action.Container',
 | 
			
		||||
                                'x-component-props': {
 | 
			
		||||
                                  className: 'nb-action-popup',
 | 
			
		||||
                                },
 | 
			
		||||
                                properties: {
 | 
			
		||||
                                  tabs: {
 | 
			
		||||
                                    _isJSONSchemaObject: true,
 | 
			
		||||
                                    version: '2.0',
 | 
			
		||||
                                    type: 'void',
 | 
			
		||||
                                    'x-component': 'Tabs',
 | 
			
		||||
                                    'x-component-props': {},
 | 
			
		||||
                                    'x-initializer': 'TabPaneInitializersForCreateFormBlock',
 | 
			
		||||
                                    properties: {
 | 
			
		||||
                                      tab1: {
 | 
			
		||||
                                        _isJSONSchemaObject: true,
 | 
			
		||||
                                        version: '2.0',
 | 
			
		||||
                                        type: 'void',
 | 
			
		||||
                                        title: '{{t("Add record")}}',
 | 
			
		||||
                                        'x-component': 'Tabs.TabPane',
 | 
			
		||||
                                        'x-designer': 'Tabs.Designer',
 | 
			
		||||
                                        'x-component-props': {},
 | 
			
		||||
                                        properties: {
 | 
			
		||||
                                          grid: {
 | 
			
		||||
                                            _isJSONSchemaObject: true,
 | 
			
		||||
                                            version: '2.0',
 | 
			
		||||
                                            type: 'void',
 | 
			
		||||
                                            'x-component': 'Grid',
 | 
			
		||||
                                            'x-initializer': 'CusomeizeCreateFormBlockInitializers',
 | 
			
		||||
                                            properties: {
 | 
			
		||||
                                              '4sdqrhx1egh': {
 | 
			
		||||
                                                _isJSONSchemaObject: true,
 | 
			
		||||
                                                version: '2.0',
 | 
			
		||||
                                                type: 'void',
 | 
			
		||||
                                                'x-component': 'Grid.Row',
 | 
			
		||||
                                                properties: {
 | 
			
		||||
                                                  m1gl7ido4xg: {
 | 
			
		||||
                                                    _isJSONSchemaObject: true,
 | 
			
		||||
                                                    version: '2.0',
 | 
			
		||||
                                                    type: 'void',
 | 
			
		||||
                                                    'x-component': 'Grid.Col',
 | 
			
		||||
                                                    properties: {
 | 
			
		||||
                                                      f0srs8s7mq6: {
 | 
			
		||||
                                                        _isJSONSchemaObject: true,
 | 
			
		||||
                                                        version: '2.0',
 | 
			
		||||
                                                        type: 'void',
 | 
			
		||||
                                                        'x-acl-action-props': {
 | 
			
		||||
                                                          skipScopeCheck: true,
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                        'x-acl-action': 't_cyjvg3xxak7:create',
 | 
			
		||||
                                                        'x-decorator': 'FormBlockProvider',
 | 
			
		||||
                                                        'x-decorator-props': {
 | 
			
		||||
                                                          isCusomeizeCreate: true,
 | 
			
		||||
                                                          resource: 't_cyjvg3xxak7',
 | 
			
		||||
                                                          collection: 't_cyjvg3xxak7',
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                        'x-designer': 'FormV2.Designer',
 | 
			
		||||
                                                        'x-component': 'CardItem',
 | 
			
		||||
                                                        'x-component-props': {},
 | 
			
		||||
                                                        properties: {
 | 
			
		||||
                                                          l3kfu43aq17: {
 | 
			
		||||
                                                            _isJSONSchemaObject: true,
 | 
			
		||||
                                                            version: '2.0',
 | 
			
		||||
                                                            type: 'void',
 | 
			
		||||
                                                            'x-component': 'FormV2',
 | 
			
		||||
                                                            'x-component-props': {
 | 
			
		||||
                                                              useProps: '{{ useFormBlockProps }}',
 | 
			
		||||
                                                            },
 | 
			
		||||
                                                            properties: {
 | 
			
		||||
                                                              grid: {
 | 
			
		||||
                                                                _isJSONSchemaObject: true,
 | 
			
		||||
                                                                version: '2.0',
 | 
			
		||||
                                                                type: 'void',
 | 
			
		||||
                                                                'x-component': 'Grid',
 | 
			
		||||
                                                                'x-initializer': 'FormItemInitializers',
 | 
			
		||||
                                                                properties: {
 | 
			
		||||
                                                                  j57m9wdhidh: {
 | 
			
		||||
                                                                    _isJSONSchemaObject: true,
 | 
			
		||||
                                                                    version: '2.0',
 | 
			
		||||
                                                                    type: 'void',
 | 
			
		||||
                                                                    'x-component': 'Grid.Row',
 | 
			
		||||
                                                                    properties: {
 | 
			
		||||
                                                                      '8l5ut3li3my': {
 | 
			
		||||
                                                                        _isJSONSchemaObject: true,
 | 
			
		||||
                                                                        version: '2.0',
 | 
			
		||||
                                                                        type: 'void',
 | 
			
		||||
                                                                        'x-component': 'Grid.Col',
 | 
			
		||||
                                                                        properties: {
 | 
			
		||||
                                                                          f_wrlootg9xu9: {
 | 
			
		||||
                                                                            'x-uid': 'enypaahwvzs',
 | 
			
		||||
                                                                            _isJSONSchemaObject: true,
 | 
			
		||||
                                                                            version: '2.0',
 | 
			
		||||
                                                                            type: 'string',
 | 
			
		||||
                                                                            'x-designer': 'FormItem.Designer',
 | 
			
		||||
                                                                            'x-component': 'CollectionField',
 | 
			
		||||
                                                                            'x-decorator': 'FormItem',
 | 
			
		||||
                                                                            'x-collection-field':
 | 
			
		||||
                                                                              't_cyjvg3xxak7.f_wrlootg9xu9',
 | 
			
		||||
                                                                            'x-component-props': {
 | 
			
		||||
                                                                              mode: 'SubTable',
 | 
			
		||||
                                                                            },
 | 
			
		||||
                                                                            default: null,
 | 
			
		||||
                                                                            properties: {
 | 
			
		||||
                                                                              svaxqhd7v29: {
 | 
			
		||||
                                                                                _isJSONSchemaObject: true,
 | 
			
		||||
                                                                                version: '2.0',
 | 
			
		||||
                                                                                type: 'void',
 | 
			
		||||
                                                                                'x-component':
 | 
			
		||||
                                                                                  'AssociationField.SubTable',
 | 
			
		||||
                                                                                'x-initializer':
 | 
			
		||||
                                                                                  'TableColumnInitializers',
 | 
			
		||||
                                                                                'x-initializer-props': {
 | 
			
		||||
                                                                                  action: false,
 | 
			
		||||
                                                                                },
 | 
			
		||||
                                                                                'x-index': 1,
 | 
			
		||||
                                                                                properties: {
 | 
			
		||||
                                                                                  eevxwy50bcp: {
 | 
			
		||||
                                                                                    _isJSONSchemaObject: true,
 | 
			
		||||
                                                                                    version: '2.0',
 | 
			
		||||
                                                                                    type: 'void',
 | 
			
		||||
                                                                                    'x-decorator':
 | 
			
		||||
                                                                                      'TableV2.Column.Decorator',
 | 
			
		||||
                                                                                    'x-designer':
 | 
			
		||||
                                                                                      'TableV2.Column.Designer',
 | 
			
		||||
                                                                                    'x-component': 'TableV2.Column',
 | 
			
		||||
                                                                                    properties: {
 | 
			
		||||
                                                                                      f_4xulfnosvlx: {
 | 
			
		||||
                                                                                        'x-uid': 'ho3gwsra9qk',
 | 
			
		||||
                                                                                        _isJSONSchemaObject: true,
 | 
			
		||||
                                                                                        version: '2.0',
 | 
			
		||||
                                                                                        'x-collection-field':
 | 
			
		||||
                                                                                          't_cyjvg3xxak7.f_4xulfnosvlx',
 | 
			
		||||
                                                                                        'x-component':
 | 
			
		||||
                                                                                          'CollectionField',
 | 
			
		||||
                                                                                        'x-component-props': {
 | 
			
		||||
                                                                                          ellipsis: true,
 | 
			
		||||
                                                                                          size: 'small',
 | 
			
		||||
                                                                                        },
 | 
			
		||||
                                                                                        'x-decorator': 'FormItem',
 | 
			
		||||
                                                                                        'x-decorator-props': {
 | 
			
		||||
                                                                                          labelStyle: {
 | 
			
		||||
                                                                                            display: 'none',
 | 
			
		||||
                                                                                          },
 | 
			
		||||
                                                                                        },
 | 
			
		||||
                                                                                        default:
 | 
			
		||||
                                                                                          '{{$context.f_4xulfnosvlx}}',
 | 
			
		||||
                                                                                        'x-async': false,
 | 
			
		||||
                                                                                        'x-index': 1,
 | 
			
		||||
                                                                                      },
 | 
			
		||||
                                                                                    },
 | 
			
		||||
                                                                                    'x-uid': '6ym1xpsxmyf',
 | 
			
		||||
                                                                                    'x-async': false,
 | 
			
		||||
                                                                                    'x-index': 1,
 | 
			
		||||
                                                                                  },
 | 
			
		||||
                                                                                },
 | 
			
		||||
                                                                                'x-uid': 'nmbnils3epp',
 | 
			
		||||
                                                                                'x-async': false,
 | 
			
		||||
                                                                              },
 | 
			
		||||
                                                                            },
 | 
			
		||||
                                                                            'x-async': false,
 | 
			
		||||
                                                                            'x-index': 1,
 | 
			
		||||
                                                                          },
 | 
			
		||||
                                                                        },
 | 
			
		||||
                                                                        'x-uid': 'e69smhckndt',
 | 
			
		||||
                                                                        'x-async': false,
 | 
			
		||||
                                                                        'x-index': 1,
 | 
			
		||||
                                                                      },
 | 
			
		||||
                                                                    },
 | 
			
		||||
                                                                    'x-uid': 'puxwfyhomzf',
 | 
			
		||||
                                                                    'x-async': false,
 | 
			
		||||
                                                                    'x-index': 1,
 | 
			
		||||
                                                                  },
 | 
			
		||||
                                                                },
 | 
			
		||||
                                                                'x-uid': 'rziak1zqrgj',
 | 
			
		||||
                                                                'x-async': false,
 | 
			
		||||
                                                                'x-index': 1,
 | 
			
		||||
                                                              },
 | 
			
		||||
                                                              actions: {
 | 
			
		||||
                                                                _isJSONSchemaObject: true,
 | 
			
		||||
                                                                version: '2.0',
 | 
			
		||||
                                                                type: 'void',
 | 
			
		||||
                                                                'x-initializer': 'FormActionInitializers',
 | 
			
		||||
                                                                'x-component': 'ActionBar',
 | 
			
		||||
                                                                'x-component-props': {
 | 
			
		||||
                                                                  layout: 'one-column',
 | 
			
		||||
                                                                  style: {
 | 
			
		||||
                                                                    marginTop: 24,
 | 
			
		||||
                                                                  },
 | 
			
		||||
                                                                },
 | 
			
		||||
                                                                'x-uid': 'ohkberml3rb',
 | 
			
		||||
                                                                'x-async': false,
 | 
			
		||||
                                                                'x-index': 2,
 | 
			
		||||
                                                              },
 | 
			
		||||
                                                            },
 | 
			
		||||
                                                            'x-uid': 't2zwt2anafy',
 | 
			
		||||
                                                            'x-async': false,
 | 
			
		||||
                                                            'x-index': 1,
 | 
			
		||||
                                                          },
 | 
			
		||||
                                                        },
 | 
			
		||||
                                                        'x-uid': '9nr7zawsp77',
 | 
			
		||||
                                                        'x-async': false,
 | 
			
		||||
                                                        'x-index': 1,
 | 
			
		||||
                                                      },
 | 
			
		||||
                                                    },
 | 
			
		||||
                                                    'x-uid': 'sy2eoiiguig',
 | 
			
		||||
                                                    'x-async': false,
 | 
			
		||||
                                                    'x-index': 1,
 | 
			
		||||
                                                  },
 | 
			
		||||
                                                },
 | 
			
		||||
                                                'x-uid': '3dpkedn8cdb',
 | 
			
		||||
                                                'x-async': false,
 | 
			
		||||
                                                'x-index': 1,
 | 
			
		||||
                                              },
 | 
			
		||||
                                            },
 | 
			
		||||
                                            'x-uid': 'vkzbih8qgqz',
 | 
			
		||||
                                            'x-async': false,
 | 
			
		||||
                                            'x-index': 1,
 | 
			
		||||
                                          },
 | 
			
		||||
                                        },
 | 
			
		||||
                                        'x-uid': '88pixfaj3wm',
 | 
			
		||||
                                        'x-async': false,
 | 
			
		||||
                                        'x-index': 1,
 | 
			
		||||
                                      },
 | 
			
		||||
                                    },
 | 
			
		||||
                                    'x-uid': 'khgiclrgqhp',
 | 
			
		||||
                                    'x-async': false,
 | 
			
		||||
                                    'x-index': 1,
 | 
			
		||||
                                  },
 | 
			
		||||
                                },
 | 
			
		||||
                                'x-uid': 'p5j5303aqdv',
 | 
			
		||||
                                'x-async': false,
 | 
			
		||||
                                'x-index': 1,
 | 
			
		||||
                              },
 | 
			
		||||
                            },
 | 
			
		||||
                            'x-uid': 'jcdnkjqfof4',
 | 
			
		||||
                            'x-async': false,
 | 
			
		||||
                            'x-index': 1,
 | 
			
		||||
                          },
 | 
			
		||||
                        },
 | 
			
		||||
                        'x-uid': 'wmcdk2bf0rk',
 | 
			
		||||
                        'x-async': false,
 | 
			
		||||
                        'x-index': 1,
 | 
			
		||||
                      },
 | 
			
		||||
                      ooh4p68w3wy: {
 | 
			
		||||
                        _isJSONSchemaObject: true,
 | 
			
		||||
                        version: '2.0',
 | 
			
		||||
                        type: 'array',
 | 
			
		||||
                        'x-initializer': 'TableColumnInitializers',
 | 
			
		||||
                        'x-component': 'TableV2',
 | 
			
		||||
                        'x-component-props': {
 | 
			
		||||
                          rowKey: 'id',
 | 
			
		||||
                          rowSelection: {
 | 
			
		||||
                            type: 'checkbox',
 | 
			
		||||
                          },
 | 
			
		||||
                          useProps: '{{ useTableBlockProps }}',
 | 
			
		||||
                        },
 | 
			
		||||
                        properties: {
 | 
			
		||||
                          actions: {
 | 
			
		||||
                            _isJSONSchemaObject: true,
 | 
			
		||||
                            version: '2.0',
 | 
			
		||||
                            type: 'void',
 | 
			
		||||
                            title: '{{ t("Actions") }}',
 | 
			
		||||
                            'x-action-column': 'actions',
 | 
			
		||||
                            'x-decorator': 'TableV2.Column.ActionBar',
 | 
			
		||||
                            'x-component': 'TableV2.Column',
 | 
			
		||||
                            'x-designer': 'TableV2.ActionColumnDesigner',
 | 
			
		||||
                            'x-initializer': 'TableActionColumnInitializers',
 | 
			
		||||
                            properties: {
 | 
			
		||||
                              actions: {
 | 
			
		||||
                                _isJSONSchemaObject: true,
 | 
			
		||||
                                version: '2.0',
 | 
			
		||||
                                type: 'void',
 | 
			
		||||
                                'x-decorator': 'DndContext',
 | 
			
		||||
                                'x-component': 'Space',
 | 
			
		||||
                                'x-component-props': {
 | 
			
		||||
                                  split: '|',
 | 
			
		||||
                                },
 | 
			
		||||
                                'x-uid': 'zf80uv8uhol',
 | 
			
		||||
                                'x-async': false,
 | 
			
		||||
                                'x-index': 1,
 | 
			
		||||
                              },
 | 
			
		||||
                            },
 | 
			
		||||
                            'x-uid': 'lrb33ra71kx',
 | 
			
		||||
                            'x-async': false,
 | 
			
		||||
                            'x-index': 1,
 | 
			
		||||
                          },
 | 
			
		||||
                          bi9qnla6pey: {
 | 
			
		||||
                            _isJSONSchemaObject: true,
 | 
			
		||||
                            version: '2.0',
 | 
			
		||||
                            type: 'void',
 | 
			
		||||
                            'x-decorator': 'TableV2.Column.Decorator',
 | 
			
		||||
                            'x-designer': 'TableV2.Column.Designer',
 | 
			
		||||
                            'x-component': 'TableV2.Column',
 | 
			
		||||
                            properties: {
 | 
			
		||||
                              f_4xulfnosvlx: {
 | 
			
		||||
                                _isJSONSchemaObject: true,
 | 
			
		||||
                                version: '2.0',
 | 
			
		||||
                                'x-collection-field': 't_cyjvg3xxak7.f_4xulfnosvlx',
 | 
			
		||||
                                'x-component': 'CollectionField',
 | 
			
		||||
                                'x-component-props': {
 | 
			
		||||
                                  ellipsis: true,
 | 
			
		||||
                                  size: 'small',
 | 
			
		||||
                                },
 | 
			
		||||
                                'x-read-pretty': true,
 | 
			
		||||
                                'x-decorator': null,
 | 
			
		||||
                                'x-decorator-props': {
 | 
			
		||||
                                  labelStyle: {
 | 
			
		||||
                                    display: 'none',
 | 
			
		||||
                                  },
 | 
			
		||||
                                },
 | 
			
		||||
                                'x-uid': '9a1xgqcn8g8',
 | 
			
		||||
                                'x-async': false,
 | 
			
		||||
                                'x-index': 1,
 | 
			
		||||
                              },
 | 
			
		||||
                            },
 | 
			
		||||
                            'x-uid': 'ipt9o4tqqkw',
 | 
			
		||||
                            'x-async': false,
 | 
			
		||||
                            'x-index': 2,
 | 
			
		||||
                          },
 | 
			
		||||
                        },
 | 
			
		||||
                        'x-uid': 'a89hczfm5nt',
 | 
			
		||||
                        'x-async': false,
 | 
			
		||||
                        'x-index': 2,
 | 
			
		||||
                      },
 | 
			
		||||
                    },
 | 
			
		||||
                    'x-uid': 'sghu92e6os9',
 | 
			
		||||
                    'x-async': false,
 | 
			
		||||
                    'x-index': 1,
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
                'x-uid': '8bhcc7f8dfb',
 | 
			
		||||
                'x-async': false,
 | 
			
		||||
                'x-index': 1,
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            'x-uid': 'l4nrtmguqpv',
 | 
			
		||||
            'x-async': false,
 | 
			
		||||
            'x-index': 1,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        'x-uid': 'jfcdfwu92mu',
 | 
			
		||||
        'x-async': false,
 | 
			
		||||
        'x-index': 1,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    'x-uid': 'cfjrdgvlm68',
 | 
			
		||||
    'x-async': true,
 | 
			
		||||
    'x-index': 1,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
test.describe.skip('variables', () => {
 | 
			
		||||
  test('T-2072', async ({ page, mockPage }) => {
 | 
			
		||||
    await mockPage(t2072Config).goto();
 | 
			
		||||
 | 
			
		||||
    await page.getByTestId('table-index-1').hover();
 | 
			
		||||
    await page.getByRole('cell', { name: '1' }).getByLabel('').check();
 | 
			
		||||
    await page.getByTestId('table-index-2').hover();
 | 
			
		||||
    await page.getByRole('cell', { name: '2' }).getByLabel('').check();
 | 
			
		||||
    await page.getByTestId('table-index-3').hover();
 | 
			
		||||
    await page.getByRole('cell', { name: '3' }).getByLabel('').check();
 | 
			
		||||
    await page.getByTestId('customize:create-action').click();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { Field, GeneralField } from '@formily/core';
 | 
			
		||||
import { RecursionField, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useRequest } from 'ahooks';
 | 
			
		||||
import { Col, Row } from 'antd';
 | 
			
		||||
@ -24,7 +24,16 @@ import { useAssociationNames } from './hooks';
 | 
			
		||||
 | 
			
		||||
export const BlockResourceContext = createContext(null);
 | 
			
		||||
export const BlockAssociationContext = createContext(null);
 | 
			
		||||
export const BlockRequestContext = createContext<any>({});
 | 
			
		||||
export const BlockRequestContext = createContext<{
 | 
			
		||||
  block?: string;
 | 
			
		||||
  props?: any;
 | 
			
		||||
  field?: GeneralField;
 | 
			
		||||
  service?: any;
 | 
			
		||||
  resource?: any;
 | 
			
		||||
  allowedActions?: any;
 | 
			
		||||
  __parent?: any;
 | 
			
		||||
  updateAssociationValues?: any[];
 | 
			
		||||
}>({});
 | 
			
		||||
 | 
			
		||||
export const useBlockResource = () => {
 | 
			
		||||
  return useContext(BlockResourceContext);
 | 
			
		||||
@ -56,7 +65,7 @@ const useResource = (props: UseResourceProps) => {
 | 
			
		||||
  const isCreateAction = fieldSchema?.['x-action'] === 'create';
 | 
			
		||||
  const association = useAssociation(props);
 | 
			
		||||
  const sourceId = useSourceId?.();
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const withoutTableFieldResource = useContext(WithoutTableFieldResource);
 | 
			
		||||
  const __parent = useContext(BlockRequestContext);
 | 
			
		||||
  if (block === 'TableField') {
 | 
			
		||||
@ -96,7 +105,7 @@ const useActionParams = (props) => {
 | 
			
		||||
  return { ...props.params, ...params };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useResourceAction = (props, opts = {}) => {
 | 
			
		||||
const useResourceAction = (props, opts = {}) => {
 | 
			
		||||
  /**
 | 
			
		||||
   * fieldName: 来自 TableFieldProvider
 | 
			
		||||
   */
 | 
			
		||||
@ -168,7 +177,7 @@ export const MaybeCollectionProvider = (props) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const BlockRequestProvider = (props) => {
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
  const resource = useBlockResource();
 | 
			
		||||
  const [allowedActions, setAllowedActions] = useState({});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import { RecordProvider, useRecord } from '../record-provider';
 | 
			
		||||
import { useActionContext, useDesignable } from '../schema-component';
 | 
			
		||||
import { Templates as DataTemplateSelect } from '../schema-component/antd/form-v2/Templates';
 | 
			
		||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
 | 
			
		||||
import { FormActiveFieldsProvider } from './hooks';
 | 
			
		||||
 | 
			
		||||
export const FormBlockContext = createContext<any>({});
 | 
			
		||||
 | 
			
		||||
@ -19,7 +20,7 @@ const InternalFormBlockProvider = (props) => {
 | 
			
		||||
      createForm({
 | 
			
		||||
        readPretty,
 | 
			
		||||
      }),
 | 
			
		||||
    [],
 | 
			
		||||
    [readPretty],
 | 
			
		||||
  );
 | 
			
		||||
  const { resource, service, updateAssociationValues } = useBlockRequestContext();
 | 
			
		||||
  const formBlockRef = useRef();
 | 
			
		||||
@ -27,6 +28,7 @@ const InternalFormBlockProvider = (props) => {
 | 
			
		||||
  if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) {
 | 
			
		||||
    return <Spin />;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FormBlockContext.Provider
 | 
			
		||||
      value={{
 | 
			
		||||
@ -83,7 +85,9 @@ export const FormBlockProvider = (props) => {
 | 
			
		||||
  return (
 | 
			
		||||
    (detailFlag || createFlag || isCusomeizeCreate) && (
 | 
			
		||||
      <BlockProvider data-testid={props['data-testid'] || 'form-block'} {...props} block={'form'}>
 | 
			
		||||
        <FormActiveFieldsProvider name="form">
 | 
			
		||||
          <InternalFormBlockProvider {...props} />
 | 
			
		||||
        </FormActiveFieldsProvider>
 | 
			
		||||
      </BlockProvider>
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
@ -91,7 +91,7 @@ export const useGanttBlockProps = () => {
 | 
			
		||||
  const { getPrimaryKey, name, template, writableView } = useCollection();
 | 
			
		||||
  const { parseAction } = useACLRoleContext();
 | 
			
		||||
  const primaryKey = getPrimaryKey();
 | 
			
		||||
  const checkPermassion = (record) => {
 | 
			
		||||
  const checkPermission = (record) => {
 | 
			
		||||
    const actionPath = `${name}:update`;
 | 
			
		||||
    const schema = {};
 | 
			
		||||
    const recordPkValue = record?.[primaryKey];
 | 
			
		||||
@ -106,13 +106,13 @@ export const useGanttBlockProps = () => {
 | 
			
		||||
    ctx.field.data = tasksData;
 | 
			
		||||
  };
 | 
			
		||||
  const expandAndCollapseAll = (flag) => {
 | 
			
		||||
    const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, flag, checkPermassion);
 | 
			
		||||
    const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, flag, checkPermission);
 | 
			
		||||
    setTasks(data);
 | 
			
		||||
    ctx.field.data = data;
 | 
			
		||||
  };
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!ctx?.service?.loading) {
 | 
			
		||||
      const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, false, checkPermassion);
 | 
			
		||||
      const data = formatData(ctx.service.data?.data, ctx.fieldNames, [], undefined, false, checkPermission);
 | 
			
		||||
      setTasks(data);
 | 
			
		||||
      ctx.field.data = data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,10 @@ interface Props {
 | 
			
		||||
  rowKey?: string;
 | 
			
		||||
  childrenColumnName: any;
 | 
			
		||||
  fieldNames?: any;
 | 
			
		||||
  /**
 | 
			
		||||
   * Table 区块的 collection name
 | 
			
		||||
   */
 | 
			
		||||
  collection?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const InternalTableBlockProvider = (props: Props) => {
 | 
			
		||||
@ -43,6 +47,7 @@ const InternalTableBlockProvider = (props: Props) => {
 | 
			
		||||
      return keys || [];
 | 
			
		||||
    }
 | 
			
		||||
  }, [service?.loading]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FixedBlockWrapper>
 | 
			
		||||
      <TableBlockContext.Provider
 | 
			
		||||
@ -94,6 +99,7 @@ export const TableBlockProvider = (props) => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const form = useMemo(() => createForm(), [treeTable]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaComponentOptions scope={{ treeTable }}>
 | 
			
		||||
      <FormContext.Provider value={form}>
 | 
			
		||||
@ -142,7 +148,6 @@ export const useTableBlockProps = () => {
 | 
			
		||||
          }
 | 
			
		||||
        : false,
 | 
			
		||||
    onRowSelectionChange(selectedRowKeys) {
 | 
			
		||||
      console.log(selectedRowKeys);
 | 
			
		||||
      ctx.field.data = ctx?.field?.data || {};
 | 
			
		||||
      ctx.field.data.selectedRowKeys = selectedRowKeys;
 | 
			
		||||
      ctx?.field?.onRowSelect?.(selectedRowKeys);
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
import { SchemaExpressionScopeContext, useField, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import { isURL, parse } from '@nocobase/utils/client';
 | 
			
		||||
import { App, message } from 'antd';
 | 
			
		||||
import { cloneDeep } from 'lodash';
 | 
			
		||||
import _, { cloneDeep } from 'lodash';
 | 
			
		||||
import get from 'lodash/get';
 | 
			
		||||
import omit from 'lodash/omit';
 | 
			
		||||
import { ChangeEvent, useContext, useEffect } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useNavigate } from 'react-router-dom';
 | 
			
		||||
import { useReactToPrint } from 'react-to-print';
 | 
			
		||||
import { AssociationFilter, useFormBlockContext, useTableBlockContext } from '../..';
 | 
			
		||||
import { AssociationFilter, useFormActiveFields, useFormBlockContext, useTableBlockContext } from '../..';
 | 
			
		||||
import { useAPIClient, useRequest } from '../../api-client';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../collection-manager';
 | 
			
		||||
import { useFilterBlock } from '../../filter-provider/FilterProvider';
 | 
			
		||||
@ -17,11 +17,16 @@ import { useRecord } from '../../record-provider';
 | 
			
		||||
import { removeNullCondition, useActionContext, useCompile } from '../../schema-component';
 | 
			
		||||
import { BulkEditFormItemValueType } from '../../schema-initializer/components';
 | 
			
		||||
import { useCurrentUserContext } from '../../user';
 | 
			
		||||
import { useLocalVariables, useVariables } from '../../variables';
 | 
			
		||||
import { isVariable } from '../../variables/utils/isVariable';
 | 
			
		||||
import { transformVariableValue } from '../../variables/utils/transformVariableValue';
 | 
			
		||||
import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../BlockProvider';
 | 
			
		||||
import { useDetailsBlockContext } from '../DetailsBlockProvider';
 | 
			
		||||
import { mergeFilter } from '../SharedFilterProvider';
 | 
			
		||||
import { TableFieldResource } from '../TableFieldProvider';
 | 
			
		||||
 | 
			
		||||
export * from './useFormActiveFields';
 | 
			
		||||
 | 
			
		||||
export const usePickActionProps = () => {
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  return {
 | 
			
		||||
@ -61,13 +66,28 @@ const filterValue = (value) => {
 | 
			
		||||
  return obj;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getFormValues(filterByTk, field, form, fieldNames, getField, resource) {
 | 
			
		||||
function getFormValues({
 | 
			
		||||
  filterByTk,
 | 
			
		||||
  field,
 | 
			
		||||
  form,
 | 
			
		||||
  fieldNames,
 | 
			
		||||
  getField,
 | 
			
		||||
  resource,
 | 
			
		||||
  actionFields,
 | 
			
		||||
}: {
 | 
			
		||||
  filterByTk;
 | 
			
		||||
  field;
 | 
			
		||||
  form;
 | 
			
		||||
  fieldNames;
 | 
			
		||||
  getField;
 | 
			
		||||
  resource;
 | 
			
		||||
  actionFields: any[];
 | 
			
		||||
}) {
 | 
			
		||||
  if (filterByTk) {
 | 
			
		||||
    const actionFields = field?.data?.activeFields as Set<string>;
 | 
			
		||||
    if (actionFields) {
 | 
			
		||||
      const keys = Object.keys(form.values).filter((key) => {
 | 
			
		||||
        const f = getField(key);
 | 
			
		||||
        return !actionFields.has(key) && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(f?.type);
 | 
			
		||||
        return !actionFields.includes(key) && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(f?.type);
 | 
			
		||||
      });
 | 
			
		||||
      return omit({ ...form.values }, keys);
 | 
			
		||||
    }
 | 
			
		||||
@ -122,17 +142,17 @@ export const useCreateActionProps = () => {
 | 
			
		||||
  const { field, resource, __parent } = useBlockRequestContext();
 | 
			
		||||
  const { setVisible, fieldSchema } = useActionContext();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const actionSchema = useFieldSchema();
 | 
			
		||||
  const actionField = useField();
 | 
			
		||||
  const { fields, getField, getTreeParentField } = useCollection();
 | 
			
		||||
  const { fields, getField, getTreeParentField, name } = useCollection();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const filterByTk = useFilterByTk();
 | 
			
		||||
  const currentRecord = useRecord();
 | 
			
		||||
  const currentUserContext = useCurrentUserContext();
 | 
			
		||||
  const { modal } = App.useApp();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables({ currentForm: form });
 | 
			
		||||
  const { getActiveFieldsName } = useFormActiveFields();
 | 
			
		||||
 | 
			
		||||
  const currentUser = currentUserContext?.data?.data;
 | 
			
		||||
  const action = actionField.componentProps.saveMode || 'create';
 | 
			
		||||
  const filterKeys = actionField.componentProps.filterKeys?.checked || [];
 | 
			
		||||
  return {
 | 
			
		||||
@ -146,11 +166,41 @@ export const useCreateActionProps = () => {
 | 
			
		||||
        triggerWorkflows,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const addChild = fieldSchema?.['x-component-props']?.addChild;
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
 | 
			
		||||
      const assignedValues = {};
 | 
			
		||||
      const waitList = Object.keys(originalAssignedValues).map(async (key) => {
 | 
			
		||||
        const value = originalAssignedValues[key];
 | 
			
		||||
        const collectionField = getField(key);
 | 
			
		||||
 | 
			
		||||
        if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
          if (!collectionField) {
 | 
			
		||||
            throw new Error(`useCreateActionProps: field "${key}" not found in collection "${name}"`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isVariable(value)) {
 | 
			
		||||
          const result = await variables?.parseVariable(value, localVariables);
 | 
			
		||||
          if (result) {
 | 
			
		||||
            assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
 | 
			
		||||
          }
 | 
			
		||||
        } else if (value != null && value !== '') {
 | 
			
		||||
          assignedValues[key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      await Promise.all(waitList);
 | 
			
		||||
 | 
			
		||||
      if (!skipValidator) {
 | 
			
		||||
        await form.submit();
 | 
			
		||||
      }
 | 
			
		||||
      const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
 | 
			
		||||
      const values = getFormValues({
 | 
			
		||||
        filterByTk,
 | 
			
		||||
        field,
 | 
			
		||||
        form,
 | 
			
		||||
        fieldNames,
 | 
			
		||||
        getField,
 | 
			
		||||
        resource,
 | 
			
		||||
        actionFields: getActiveFieldsName('form'),
 | 
			
		||||
      });
 | 
			
		||||
      // const values = omitBy(formValues, (value) => isEqual(JSON.stringify(value), '[{}]'));
 | 
			
		||||
      if (addChild) {
 | 
			
		||||
        const treeParentField = getTreeParentField();
 | 
			
		||||
@ -209,12 +259,14 @@ export const useAssociationCreateActionProps = () => {
 | 
			
		||||
  const { setVisible, fieldSchema } = useActionContext();
 | 
			
		||||
  const actionSchema = useFieldSchema();
 | 
			
		||||
  const actionField = useField();
 | 
			
		||||
  const { fields, getField, getTreeParentField } = useCollection();
 | 
			
		||||
  const { fields, getField, getTreeParentField, name } = useCollection();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const filterByTk = useFilterByTk();
 | 
			
		||||
  const currentRecord = useRecord();
 | 
			
		||||
  const currentUserContext = useCurrentUserContext();
 | 
			
		||||
  const currentUser = currentUserContext?.data?.data;
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables({ currentForm: form });
 | 
			
		||||
  const { getActiveFieldsName } = useFormActiveFields();
 | 
			
		||||
 | 
			
		||||
  const action = actionField.componentProps.saveMode || 'create';
 | 
			
		||||
  const filterKeys = actionField.componentProps.filterKeys?.checked || [];
 | 
			
		||||
  return {
 | 
			
		||||
@ -228,11 +280,41 @@ export const useAssociationCreateActionProps = () => {
 | 
			
		||||
        triggerWorkflows,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const addChild = fieldSchema?.['x-component-props']?.addChild;
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
 | 
			
		||||
      const assignedValues = {};
 | 
			
		||||
      const waitList = Object.keys(originalAssignedValues).map(async (key) => {
 | 
			
		||||
        const value = originalAssignedValues[key];
 | 
			
		||||
        const collectionField = getField(key);
 | 
			
		||||
 | 
			
		||||
        if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
          if (!collectionField) {
 | 
			
		||||
            throw new Error(`useAssociationCreateActionProps: field "${key}" not found in collection "${name}"`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isVariable(value)) {
 | 
			
		||||
          const result = await variables?.parseVariable(value, localVariables);
 | 
			
		||||
          if (result) {
 | 
			
		||||
            assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
 | 
			
		||||
          }
 | 
			
		||||
        } else if (value != null && value !== '') {
 | 
			
		||||
          assignedValues[key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      await Promise.all(waitList);
 | 
			
		||||
 | 
			
		||||
      if (!skipValidator) {
 | 
			
		||||
        await form.submit();
 | 
			
		||||
      }
 | 
			
		||||
      const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
 | 
			
		||||
      const values = getFormValues({
 | 
			
		||||
        filterByTk,
 | 
			
		||||
        field,
 | 
			
		||||
        form,
 | 
			
		||||
        fieldNames,
 | 
			
		||||
        getField,
 | 
			
		||||
        resource,
 | 
			
		||||
        actionFields: getActiveFieldsName('form'),
 | 
			
		||||
      });
 | 
			
		||||
      if (addChild) {
 | 
			
		||||
        const treeParentField = getTreeParentField();
 | 
			
		||||
        values[treeParentField?.name ?? 'parent'] = currentRecord;
 | 
			
		||||
@ -405,13 +487,13 @@ export const useCustomizeUpdateActionProps = () => {
 | 
			
		||||
  const { resource, __parent, service } = useBlockRequestContext();
 | 
			
		||||
  const filterByTk = useFilterByTk();
 | 
			
		||||
  const actionSchema = useFieldSchema();
 | 
			
		||||
  const currentRecord = useRecord();
 | 
			
		||||
  const currentUserContext = useCurrentUserContext();
 | 
			
		||||
  const currentUser = currentUserContext?.data?.data;
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  const { modal } = App.useApp();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables({ currentForm: form });
 | 
			
		||||
  const { name, getField } = useCollection();
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
@ -420,7 +502,29 @@ export const useCustomizeUpdateActionProps = () => {
 | 
			
		||||
        onSuccess,
 | 
			
		||||
        skipValidator,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
 | 
			
		||||
      const assignedValues = {};
 | 
			
		||||
      const waitList = Object.keys(originalAssignedValues).map(async (key) => {
 | 
			
		||||
        const value = originalAssignedValues[key];
 | 
			
		||||
        const collectionField = getField(key);
 | 
			
		||||
 | 
			
		||||
        if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
          if (!collectionField) {
 | 
			
		||||
            throw new Error(`useCustomizeUpdateActionProps: field "${key}" not found in collection "${name}"`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isVariable(value)) {
 | 
			
		||||
          const result = await variables?.parseVariable(value, localVariables);
 | 
			
		||||
          if (result) {
 | 
			
		||||
            assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
 | 
			
		||||
          }
 | 
			
		||||
        } else if (value != null && value !== '') {
 | 
			
		||||
          assignedValues[key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      await Promise.all(waitList);
 | 
			
		||||
 | 
			
		||||
      if (skipValidator === false) {
 | 
			
		||||
        await form.submit();
 | 
			
		||||
      }
 | 
			
		||||
@ -463,13 +567,14 @@ export const useCustomizeBulkUpdateActionProps = () => {
 | 
			
		||||
  const { rowKey } = tableBlockContext;
 | 
			
		||||
  const selectedRecordKeys =
 | 
			
		||||
    tableBlockContext.field?.data?.selectedRowKeys ?? expressionScope?.selectedRecordKeys ?? {};
 | 
			
		||||
  const currentUserContext = useCurrentUserContext();
 | 
			
		||||
  const currentUser = currentUserContext?.data?.data;
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const actionField = useField();
 | 
			
		||||
  const { modal } = App.useApp();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables();
 | 
			
		||||
  const { name, getField } = useCollection();
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
@ -480,7 +585,29 @@ export const useCustomizeBulkUpdateActionProps = () => {
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      actionField.data = field.data || {};
 | 
			
		||||
      actionField.data.loading = true;
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentUser });
 | 
			
		||||
 | 
			
		||||
      const assignedValues = {};
 | 
			
		||||
      const waitList = Object.keys(originalAssignedValues).map(async (key) => {
 | 
			
		||||
        const value = originalAssignedValues[key];
 | 
			
		||||
        const collectionField = getField(key);
 | 
			
		||||
 | 
			
		||||
        if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
          if (!collectionField) {
 | 
			
		||||
            throw new Error(`useCustomizeBulkUpdateActionProps: field "${key}" not found in collection "${name}"`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isVariable(value)) {
 | 
			
		||||
          const result = await variables?.parseVariable(value, localVariables);
 | 
			
		||||
          if (result) {
 | 
			
		||||
            assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
 | 
			
		||||
          }
 | 
			
		||||
        } else if (value != null && value !== '') {
 | 
			
		||||
          assignedValues[key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      await Promise.all(waitList);
 | 
			
		||||
 | 
			
		||||
      modal.confirm({
 | 
			
		||||
        title: t('Bulk update'),
 | 
			
		||||
        content: updateMode === 'selected' ? t('Update selected data?') : t('Update all data?'),
 | 
			
		||||
@ -644,6 +771,7 @@ export const useCustomizeRequestActionProps = () => {
 | 
			
		||||
  const actionField = useField();
 | 
			
		||||
  const { setVisible } = useActionContext();
 | 
			
		||||
  const { modal } = App.useApp();
 | 
			
		||||
  const { getActiveFieldsName } = useFormActiveFields();
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
@ -662,7 +790,15 @@ export const useCustomizeRequestActionProps = () => {
 | 
			
		||||
      const methods = ['POST', 'PUT', 'PATCH'];
 | 
			
		||||
      if (xAction === 'customize:form:request' && methods.includes(requestSettings['method'])) {
 | 
			
		||||
        const fieldNames = fields.map((field) => field.name);
 | 
			
		||||
        const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
 | 
			
		||||
        const values = getFormValues({
 | 
			
		||||
          filterByTk,
 | 
			
		||||
          field,
 | 
			
		||||
          form,
 | 
			
		||||
          fieldNames,
 | 
			
		||||
          getField,
 | 
			
		||||
          resource,
 | 
			
		||||
          actionFields: getActiveFieldsName('form'),
 | 
			
		||||
        });
 | 
			
		||||
        Object.assign(data, values);
 | 
			
		||||
      }
 | 
			
		||||
      const requestBody = {
 | 
			
		||||
@ -715,20 +851,19 @@ export const useCustomizeRequestActionProps = () => {
 | 
			
		||||
export const useUpdateActionProps = () => {
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  const filterByTk = useFilterByTk();
 | 
			
		||||
  const { field, resource, __parent, service } = useBlockRequestContext();
 | 
			
		||||
  const { field, resource, __parent } = useBlockRequestContext();
 | 
			
		||||
  const { setVisible } = useActionContext();
 | 
			
		||||
  const actionSchema = useFieldSchema();
 | 
			
		||||
  const navigate = useNavigate();
 | 
			
		||||
  const { fields, getField } = useCollection();
 | 
			
		||||
  const { fields, getField, name } = useCollection();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const actionField = useField();
 | 
			
		||||
  const { updateAssociationValues } = useFormBlockContext();
 | 
			
		||||
  const currentRecord = useRecord();
 | 
			
		||||
  const currentUserContext = useCurrentUserContext();
 | 
			
		||||
  const { modal } = App.useApp();
 | 
			
		||||
 | 
			
		||||
  const currentUser = currentUserContext?.data?.data;
 | 
			
		||||
  const data = useParamsFromRecord();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables({ currentForm: form });
 | 
			
		||||
  const { getActiveFieldsName } = useFormActiveFields();
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    async onClick() {
 | 
			
		||||
@ -739,12 +874,42 @@ export const useUpdateActionProps = () => {
 | 
			
		||||
        skipValidator,
 | 
			
		||||
        triggerWorkflows,
 | 
			
		||||
      } = actionSchema?.['x-action-settings'] ?? {};
 | 
			
		||||
      const assignedValues = parse(originalAssignedValues)({ currentTime: new Date(), currentRecord, currentUser });
 | 
			
		||||
 | 
			
		||||
      const assignedValues = {};
 | 
			
		||||
      const waitList = Object.keys(originalAssignedValues).map(async (key) => {
 | 
			
		||||
        const value = originalAssignedValues[key];
 | 
			
		||||
        const collectionField = getField(key);
 | 
			
		||||
 | 
			
		||||
        if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
          if (!collectionField) {
 | 
			
		||||
            throw new Error(`useUpdateActionProps: field "${key}" not found in collection "${name}"`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isVariable(value)) {
 | 
			
		||||
          const result = await variables?.parseVariable(value, localVariables);
 | 
			
		||||
          if (result) {
 | 
			
		||||
            assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField });
 | 
			
		||||
          }
 | 
			
		||||
        } else if (value != null && value !== '') {
 | 
			
		||||
          assignedValues[key] = value;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      await Promise.all(waitList);
 | 
			
		||||
 | 
			
		||||
      if (!skipValidator) {
 | 
			
		||||
        await form.submit();
 | 
			
		||||
      }
 | 
			
		||||
      const fieldNames = fields.map((field) => field.name);
 | 
			
		||||
      const values = getFormValues(filterByTk, field, form, fieldNames, getField, resource);
 | 
			
		||||
      const values = getFormValues({
 | 
			
		||||
        filterByTk,
 | 
			
		||||
        field,
 | 
			
		||||
        form,
 | 
			
		||||
        fieldNames,
 | 
			
		||||
        getField,
 | 
			
		||||
        resource,
 | 
			
		||||
        actionFields: getActiveFieldsName('form'),
 | 
			
		||||
      });
 | 
			
		||||
      actionField.data = field.data || {};
 | 
			
		||||
      actionField.data.loading = true;
 | 
			
		||||
      try {
 | 
			
		||||
@ -1125,6 +1290,25 @@ export const useAssociationNames = () => {
 | 
			
		||||
      const isAssociationSubfield = s.name.includes('.');
 | 
			
		||||
      const isAssociationField =
 | 
			
		||||
        collectionfield && ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(collectionfield.type);
 | 
			
		||||
 | 
			
		||||
      // 根据联动规则中条件的字段获取一些 appends
 | 
			
		||||
      if (s['x-linkage-rules']) {
 | 
			
		||||
        const rules = s['x-linkage-rules'];
 | 
			
		||||
        rules.forEach(({ condition }) => {
 | 
			
		||||
          const type = Object.keys(condition)[0] || '$and';
 | 
			
		||||
          const list = condition[type];
 | 
			
		||||
 | 
			
		||||
          list.forEach((item) => {
 | 
			
		||||
            const fieldNames = getTargetField(item);
 | 
			
		||||
 | 
			
		||||
            // 只应该收集关系字段,只有大于 1 的时候才是关系字段
 | 
			
		||||
            if (fieldNames.length > 1) {
 | 
			
		||||
              appends.add(fieldNames.join('.'));
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const isTreeCollection = isAssociationField && getCollection(collectionfield.target)?.template === 'tree';
 | 
			
		||||
      if (collectionfield && (isAssociationField || isAssociationSubfield) && s['x-component'] !== 'TableField') {
 | 
			
		||||
        const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name;
 | 
			
		||||
@ -1161,3 +1345,30 @@ export const useAssociationNames = () => {
 | 
			
		||||
  getAssociationAppends(fieldSchema, '');
 | 
			
		||||
  return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getTargetField(obj) {
 | 
			
		||||
  function getAllKeys(obj) {
 | 
			
		||||
    const keys = [];
 | 
			
		||||
    function traverse(o) {
 | 
			
		||||
      Object.keys(o)
 | 
			
		||||
        .sort()
 | 
			
		||||
        .forEach(function (key) {
 | 
			
		||||
          keys.push(key);
 | 
			
		||||
          if (o[key] && typeof o[key] === 'object') {
 | 
			
		||||
            traverse(o[key]);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    traverse(obj);
 | 
			
		||||
    return keys;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const keys = getAllKeys(obj);
 | 
			
		||||
  const index = _.findIndex(keys, (key: string, index: number) => {
 | 
			
		||||
    if (key.includes('$') && index > 0) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  const result = keys.slice(0, index);
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
import React, { createContext, useContext, useMemo, useRef } from 'react';
 | 
			
		||||
 | 
			
		||||
interface FormActiveFieldsProviderValue {
 | 
			
		||||
  getActiveFieldsName: (name: string) => string[];
 | 
			
		||||
  addActiveFieldName: (fieldName: string) => void;
 | 
			
		||||
  removeActiveFieldName: (fieldName: string) => void;
 | 
			
		||||
  name: string;
 | 
			
		||||
  parent: FormActiveFieldsProviderValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const FormActiveFieldsContext = createContext<FormActiveFieldsProviderValue>(null);
 | 
			
		||||
 | 
			
		||||
interface ProviderProps extends Partial<FormActiveFieldsProviderValue> {
 | 
			
		||||
  children: React.ReactNode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FormActiveFieldsProvider = ({
 | 
			
		||||
  children,
 | 
			
		||||
  name,
 | 
			
		||||
  getActiveFieldsName,
 | 
			
		||||
  addActiveFieldName,
 | 
			
		||||
  removeActiveFieldName,
 | 
			
		||||
}: ProviderProps) => {
 | 
			
		||||
  const activeFieldsNameRef = useRef<Set<string>>(new Set());
 | 
			
		||||
  const upLevelContext = useContext(FormActiveFieldsContext);
 | 
			
		||||
 | 
			
		||||
  const value = useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      getActiveFieldsName:
 | 
			
		||||
        getActiveFieldsName ||
 | 
			
		||||
        ((_name: string) => {
 | 
			
		||||
          if (!_name || _name === name) {
 | 
			
		||||
            return Array.from(activeFieldsNameRef.current);
 | 
			
		||||
          }
 | 
			
		||||
          return upLevelContext?.getActiveFieldsName(_name);
 | 
			
		||||
        }),
 | 
			
		||||
      addActiveFieldName:
 | 
			
		||||
        addActiveFieldName ||
 | 
			
		||||
        ((field: string) => {
 | 
			
		||||
          activeFieldsNameRef.current.add(field);
 | 
			
		||||
        }),
 | 
			
		||||
      removeActiveFieldName:
 | 
			
		||||
        removeActiveFieldName ||
 | 
			
		||||
        ((field: string) => {
 | 
			
		||||
          activeFieldsNameRef.current.delete(field);
 | 
			
		||||
        }),
 | 
			
		||||
      name,
 | 
			
		||||
      parent: upLevelContext,
 | 
			
		||||
    };
 | 
			
		||||
  }, [addActiveFieldName, getActiveFieldsName, name, removeActiveFieldName, upLevelContext]);
 | 
			
		||||
 | 
			
		||||
  return <FormActiveFieldsContext.Provider value={value}>{children}</FormActiveFieldsContext.Provider>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用于获取和更新当前表单区块中已显示出来的字段
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export const useFormActiveFields = () => {
 | 
			
		||||
  return useContext(FormActiveFieldsContext);
 | 
			
		||||
};
 | 
			
		||||
@ -6,7 +6,7 @@ export const CollectionManagerContext = createContext<CollectionManagerOptions>(
 | 
			
		||||
  interfaces: {},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const CollectionContext = createContext<CollectionOptions>({});
 | 
			
		||||
export const CollectionContext = createContext<CollectionOptions>({} as CollectionOptions);
 | 
			
		||||
 | 
			
		||||
export const CollectionFieldContext = createContext<CollectionFieldOptions>({});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,12 @@ import _, { reduce, unionBy, uniq, uniqBy } from 'lodash';
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
import { useCompile } from '../../schema-component';
 | 
			
		||||
import { CollectionManagerContext } from '../context';
 | 
			
		||||
import { CollectionFieldOptions } from '../types';
 | 
			
		||||
import { CollectionFieldOptions, CollectionOptions } from '../types';
 | 
			
		||||
 | 
			
		||||
export const useCollectionManager = () => {
 | 
			
		||||
  const { refreshCM, updateCollection, service, interfaces, collections, templates } =
 | 
			
		||||
    useContext(CollectionManagerContext);
 | 
			
		||||
  const { refreshCM, updateCollection, service, interfaces, collections, templates } = useContext(
 | 
			
		||||
    CollectionManagerContext,
 | 
			
		||||
  );
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const getInheritedFields = (name) => {
 | 
			
		||||
    const inheritKeys = getInheritCollections(name);
 | 
			
		||||
@ -192,7 +193,7 @@ export const useCollectionManager = () => {
 | 
			
		||||
    return options;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getCollection = (name: any) => {
 | 
			
		||||
  const getCollection = (name: any): CollectionOptions => {
 | 
			
		||||
    if (typeof name !== 'string') {
 | 
			
		||||
      return name;
 | 
			
		||||
    }
 | 
			
		||||
@ -290,7 +291,7 @@ export const useCollectionManager = () => {
 | 
			
		||||
    getCollectionFieldsOptions,
 | 
			
		||||
    getCurrentCollectionFields,
 | 
			
		||||
    getCollection,
 | 
			
		||||
    getCollectionJoinField(name: string) {
 | 
			
		||||
    getCollectionJoinField(name: string): CollectionFieldOptions {
 | 
			
		||||
      if (!name) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { FormItem, FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import { registerValidateRules } from '@formily/core';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { defaultProps } from './properties';
 | 
			
		||||
import { defaultProps, operators } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
 | 
			
		||||
registerValidateRules({
 | 
			
		||||
@ -59,5 +59,7 @@ export const json: IField = {
 | 
			
		||||
      'x-disabled': `{{ disabledJSONB }}`,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  filterable: {},
 | 
			
		||||
  filterable: {
 | 
			
		||||
    operators: operators.string,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -138,5 +138,4 @@ export const linkTo: IField = {
 | 
			
		||||
      // },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  invariable: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,6 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import {
 | 
			
		||||
  defaultProps,
 | 
			
		||||
  relationshipType,
 | 
			
		||||
  reverseFieldProperties
 | 
			
		||||
} from './properties';
 | 
			
		||||
import { defaultProps, relationshipType, reverseFieldProperties } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
 | 
			
		||||
export const m2m: IField = {
 | 
			
		||||
@ -246,5 +242,4 @@ export const m2m: IField = {
 | 
			
		||||
      // },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  invariable: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,5 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import {
 | 
			
		||||
  constraintsProps,
 | 
			
		||||
  relationshipType,
 | 
			
		||||
  reverseFieldProperties
 | 
			
		||||
} from './properties';
 | 
			
		||||
import { constraintsProps, relationshipType, reverseFieldProperties } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
 | 
			
		||||
export const m2o: IField = {
 | 
			
		||||
@ -178,5 +174,4 @@ export const m2o: IField = {
 | 
			
		||||
      // },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  invariable: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import { i18n } from '../../i18n';
 | 
			
		||||
import { defaultProps } from './properties';
 | 
			
		||||
import { defaultProps, operators } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
 | 
			
		||||
export const markdown: IField = {
 | 
			
		||||
@ -69,4 +69,7 @@ export const markdown: IField = {
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  filterable: {
 | 
			
		||||
    operators: operators.string,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -198,5 +198,4 @@ export const o2m: IField = {
 | 
			
		||||
      // },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  invariable: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -178,7 +178,6 @@ export const o2o: IField = {
 | 
			
		||||
      // },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  invariable: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const oho: IField = {
 | 
			
		||||
@ -349,7 +348,6 @@ export const oho: IField = {
 | 
			
		||||
      // },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  invariable: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const obo: IField = {
 | 
			
		||||
@ -524,5 +522,4 @@ export const obo: IField = {
 | 
			
		||||
      // },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  invariable: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -45,6 +45,11 @@ export const array = [
 | 
			
		||||
  { label: '{{t("is not empty")}}', value: '$notEmpty', noValue: true },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const object = [
 | 
			
		||||
  { label: '{{t("is")}}', value: '$eq', selected: true },
 | 
			
		||||
  { label: '{{t("is not")}}', value: '$ne' },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const datetime = [
 | 
			
		||||
  { label: "{{ t('is') }}", value: '$dateOn', selected: true },
 | 
			
		||||
  { label: "{{ t('is not') }}", value: '$dateNotOn' },
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import type { ISchema } from '@formily/react';
 | 
			
		||||
import { defaultProps } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
import { i18n } from '../../i18n';
 | 
			
		||||
import { defaultProps, operators } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
 | 
			
		||||
export const richText: IField = {
 | 
			
		||||
  name: 'richText',
 | 
			
		||||
@ -70,4 +70,7 @@ export const richText: IField = {
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  filterable: {
 | 
			
		||||
    operators: operators.string,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import { defaultProps } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
import { i18n } from '../../i18n';
 | 
			
		||||
import { defaultProps, operators } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
 | 
			
		||||
export const textarea: IField = {
 | 
			
		||||
  name: 'textarea',
 | 
			
		||||
@ -70,4 +70,7 @@ export const textarea: IField = {
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  filterable: {
 | 
			
		||||
    operators: operators.string,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,21 @@ interface IDefault {
 | 
			
		||||
export interface IField extends ISchema {
 | 
			
		||||
  default?: IDefault;
 | 
			
		||||
  operators?: any[];
 | 
			
		||||
  filterable?: any;
 | 
			
		||||
  /** 不支持使用变量的值进行设置 */
 | 
			
		||||
  invariable?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * - 如果该值为空,则在 Filter 组件中该字段会被过滤掉
 | 
			
		||||
   * - 如果该值为空,则不会在变量列表中看到该字段
 | 
			
		||||
   */
 | 
			
		||||
  filterable?: {
 | 
			
		||||
    /**
 | 
			
		||||
     * 字段所支持的操作符,会在 Filter 组件中显示,比如设置 `数据范围` 的时候可以看见
 | 
			
		||||
     */
 | 
			
		||||
    operators?: any[];
 | 
			
		||||
    /**
 | 
			
		||||
     * 为当前字段添加子选项,这个子选项会在 Filter 组件中显示,比如设置 `数据范围` 的时候可以看见
 | 
			
		||||
     */
 | 
			
		||||
    children?: any[];
 | 
			
		||||
    [key: string]: any;
 | 
			
		||||
  };
 | 
			
		||||
  // NOTE: set to `true` means field could be used as a title field
 | 
			
		||||
  titleUsable?: boolean;
 | 
			
		||||
  [key: string]: any;
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import { defaultProps } from './properties';
 | 
			
		||||
import { defaultProps, operators } from './properties';
 | 
			
		||||
import { IField } from './types';
 | 
			
		||||
 | 
			
		||||
export const url: IField = {
 | 
			
		||||
@ -20,4 +20,7 @@ export const url: IField = {
 | 
			
		||||
    ...defaultProps,
 | 
			
		||||
  },
 | 
			
		||||
  titleUsable: true,
 | 
			
		||||
  filterable: {
 | 
			
		||||
    operators: operators.string,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,7 @@ const PreviewCom = (props) => {
 | 
			
		||||
    sourceCollections.forEach((item) => {
 | 
			
		||||
      const collection = getCollection(item);
 | 
			
		||||
      const inherits = getInheritCollections(item);
 | 
			
		||||
      const result = inherits.map((v) => {
 | 
			
		||||
      const result: any[] = inherits.map((v) => {
 | 
			
		||||
        const fields = getParentCollectionFields(v, item);
 | 
			
		||||
        return {
 | 
			
		||||
          type: 'group',
 | 
			
		||||
@ -59,7 +59,7 @@ const PreviewCom = (props) => {
 | 
			
		||||
      const children = collection.fields
 | 
			
		||||
        .filter((v) => !['hasOne', 'hasMany', 'belongsToMany'].includes(v?.type))
 | 
			
		||||
        ?.map((v) => {
 | 
			
		||||
          return { value: v.name, label: t(v.uiSchema?.title) };
 | 
			
		||||
          return { value: v.name, key: v.name, label: t(v.uiSchema?.title) };
 | 
			
		||||
        })
 | 
			
		||||
        .concat(result);
 | 
			
		||||
      data.push({
 | 
			
		||||
 | 
			
		||||
@ -1,36 +1,66 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import { ReactNode } from 'react';
 | 
			
		||||
 | 
			
		||||
type dumpable = 'required' | 'optional' | 'skip';
 | 
			
		||||
type CollectionSortable = string | boolean | { name?: string; scopeKey?: string };
 | 
			
		||||
 | 
			
		||||
export interface CollectionOptions {
 | 
			
		||||
  name: string;
 | 
			
		||||
  title?: string;
 | 
			
		||||
  namespace?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Used for @nocobase/plugin-duplicator
 | 
			
		||||
   * @see packages/core/database/src/collection-group-manager.tss
 | 
			
		||||
   *
 | 
			
		||||
   * @prop {'required' | 'optional' | 'skip'} dumpable - Determine whether the collection is dumped
 | 
			
		||||
   * @prop {string[] | string} [with] - Collections dumped with this collection
 | 
			
		||||
   * @prop {any} [delayRestore] - A function to execute after all collections are restored
 | 
			
		||||
   */
 | 
			
		||||
  duplicator?:
 | 
			
		||||
    | dumpable
 | 
			
		||||
    | {
 | 
			
		||||
        dumpable: dumpable;
 | 
			
		||||
        with?: string[] | string;
 | 
			
		||||
        delayRestore?: any;
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
  tableName?: string;
 | 
			
		||||
  inherits?: string[] | string;
 | 
			
		||||
  viewName?: string;
 | 
			
		||||
  writableView?: boolean;
 | 
			
		||||
 | 
			
		||||
  filterTargetKey?: string;
 | 
			
		||||
  fields?: FieldOptions[];
 | 
			
		||||
  model?: any;
 | 
			
		||||
  repository?: any;
 | 
			
		||||
  sortable?: CollectionSortable;
 | 
			
		||||
  /**
 | 
			
		||||
   * @default true
 | 
			
		||||
   */
 | 
			
		||||
  autoGenId?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * @default 'options'
 | 
			
		||||
   */
 | 
			
		||||
  magicAttribute?: string;
 | 
			
		||||
 | 
			
		||||
  tree?: string;
 | 
			
		||||
 | 
			
		||||
  template?: string;
 | 
			
		||||
 | 
			
		||||
  [key: string]: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CollectionManagerOptions {
 | 
			
		||||
  service?: any;
 | 
			
		||||
  interfaces?: any;
 | 
			
		||||
  collections?: any[];
 | 
			
		||||
  collections?: CollectionOptions[];
 | 
			
		||||
  templates?: any;
 | 
			
		||||
  refreshCM?: () => Promise<void>;
 | 
			
		||||
  children?: ReactNode;
 | 
			
		||||
  updateCollection?: (collection: any) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface FieldOptions {
 | 
			
		||||
  type: string;
 | 
			
		||||
  interface?: string;
 | 
			
		||||
  uiSchema?: ISchema;
 | 
			
		||||
 | 
			
		||||
  [key: string]: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CollectionOptions {
 | 
			
		||||
  name?: string;
 | 
			
		||||
  title?: string;
 | 
			
		||||
  filterTargetKey?: string;
 | 
			
		||||
  targetKey?: string;
 | 
			
		||||
  sortable?: any;
 | 
			
		||||
  fields?: FieldOptions[];
 | 
			
		||||
  inherits?: string[];
 | 
			
		||||
  tree?: string;
 | 
			
		||||
  template?: string;
 | 
			
		||||
  writableView?: boolean;
 | 
			
		||||
}
 | 
			
		||||
export type FieldOptions = any;
 | 
			
		||||
 | 
			
		||||
export interface ICollectionProviderProps {
 | 
			
		||||
  name?: string;
 | 
			
		||||
 | 
			
		||||
@ -3,11 +3,11 @@ import { uniqBy } from 'lodash';
 | 
			
		||||
import React, { createContext, useEffect, useRef } from 'react';
 | 
			
		||||
import { useBlockRequestContext } from '../block-provider/BlockProvider';
 | 
			
		||||
import { SharedFilter, mergeFilter } from '../block-provider/SharedFilterProvider';
 | 
			
		||||
import { CollectionFieldOptions, FieldOptions, useCollection } from '../collection-manager';
 | 
			
		||||
import { CollectionFieldOptions, useCollection } from '../collection-manager';
 | 
			
		||||
import { removeNullCondition } from '../schema-component';
 | 
			
		||||
import { useAssociatedFields } from './utils';
 | 
			
		||||
 | 
			
		||||
export interface ForeignKeyField extends FieldOptions {
 | 
			
		||||
export interface ForeignKeyField {
 | 
			
		||||
  /** 外键字段所在的数据表的名称 */
 | 
			
		||||
  collectionName: string;
 | 
			
		||||
  isForeignKey: boolean;
 | 
			
		||||
@ -15,6 +15,8 @@ export interface ForeignKeyField extends FieldOptions {
 | 
			
		||||
  name: string;
 | 
			
		||||
  parentKey: null | string;
 | 
			
		||||
  reverseKey: null | string;
 | 
			
		||||
 | 
			
		||||
  [key: string]: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Collection = ReturnType<typeof useCollection>;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								packages/core/client/src/flag-provider/FlagProvider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/core/client/src/flag-provider/FlagProvider.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
import React, { useMemo } from 'react';
 | 
			
		||||
 | 
			
		||||
export interface FlagProviderProps {
 | 
			
		||||
  /**
 | 
			
		||||
   * 字段是否存在于 `字段赋值` 弹窗中
 | 
			
		||||
   */
 | 
			
		||||
  isInAssignFieldValues?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * 是否存在于 `设置默认值` 弹窗中
 | 
			
		||||
   */
 | 
			
		||||
  isInSetDefaultValueDialog?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * 是否存在于 `表单数据模板` 中
 | 
			
		||||
   */
 | 
			
		||||
  isInFormDataTemplate?: boolean;
 | 
			
		||||
  children: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const FlagContext = React.createContext<Omit<FlagProviderProps, 'children'>>(null);
 | 
			
		||||
 | 
			
		||||
export const FlagProvider = (props: FlagProviderProps) => {
 | 
			
		||||
  const value = useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      isInAssignFieldValues: props.isInAssignFieldValues,
 | 
			
		||||
      isInSetDefaultValueDialog: props.isInSetDefaultValueDialog,
 | 
			
		||||
      isInFormDataTemplate: props.isInFormDataTemplate,
 | 
			
		||||
    };
 | 
			
		||||
  }, [props.isInAssignFieldValues, props.isInFormDataTemplate, props.isInSetDefaultValueDialog]);
 | 
			
		||||
 | 
			
		||||
  return <FlagContext.Provider value={value}>{props.children}</FlagContext.Provider>;
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,16 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { renderHook } from 'testUtils';
 | 
			
		||||
import { FlagProvider } from '../FlagProvider';
 | 
			
		||||
import { useFlag } from '../hooks/useFlag';
 | 
			
		||||
 | 
			
		||||
describe('FlagProvider', () => {
 | 
			
		||||
  it('should render', () => {
 | 
			
		||||
    const { result } = renderHook(() => useFlag(), {
 | 
			
		||||
      wrapper: ({ children }) => {
 | 
			
		||||
        return <FlagProvider isInAssignFieldValues={true}>{children}</FlagProvider>;
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    expect(result.current.isInAssignFieldValues).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/core/client/src/flag-provider/hooks/useFlag.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/core/client/src/flag-provider/hooks/useFlag.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
import { useContext } from 'react';
 | 
			
		||||
import { FlagContext } from '../FlagProvider';
 | 
			
		||||
 | 
			
		||||
export const useFlag = () => {
 | 
			
		||||
  return useContext(FlagContext);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/core/client/src/flag-provider/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/core/client/src/flag-provider/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
export * from './FlagProvider';
 | 
			
		||||
 | 
			
		||||
export * from './hooks/useFlag';
 | 
			
		||||
@ -25,6 +25,7 @@ import {
 | 
			
		||||
import { Plugin } from '../../../application/Plugin';
 | 
			
		||||
import { useAppSpin } from '../../../application/hooks/useAppSpin';
 | 
			
		||||
import { useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { VariablesProvider } from '../../../variables';
 | 
			
		||||
 | 
			
		||||
const filterByACL = (schema, options) => {
 | 
			
		||||
  const { allowAll, allowMenuItemIds = [] } = options;
 | 
			
		||||
@ -355,7 +356,9 @@ export const AdminProvider = (props) => {
 | 
			
		||||
      <NavigateIfNotSignIn>
 | 
			
		||||
        <RemoteSchemaTemplateManagerProvider>
 | 
			
		||||
          <RemoteCollectionManagerProvider>
 | 
			
		||||
            <VariablesProvider>
 | 
			
		||||
              <ACLRolesCheckProvider>{props.children}</ACLRolesCheckProvider>
 | 
			
		||||
            </VariablesProvider>
 | 
			
		||||
          </RemoteCollectionManagerProvider>
 | 
			
		||||
        </RemoteSchemaTemplateManagerProvider>
 | 
			
		||||
      </NavigateIfNotSignIn>
 | 
			
		||||
 | 
			
		||||
@ -7,12 +7,14 @@ import { cloneDeep } from 'lodash';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { RemoteSelect, useCompile, useDesignable } from '../..';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { CollectionOptions, useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { FlagProvider } from '../../../flag-provider';
 | 
			
		||||
import { useRecord } from '../../../record-provider';
 | 
			
		||||
import { OpenModeSchemaItems } from '../../../schema-items';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks/useCollectionState';
 | 
			
		||||
import { useSyncFromForm } from '../../../schema-settings/DataTemplates/utils';
 | 
			
		||||
import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
 | 
			
		||||
import { useLinkageAction } from './hooks';
 | 
			
		||||
import { requestSettingsSchema } from './utils';
 | 
			
		||||
 | 
			
		||||
@ -481,15 +483,8 @@ function AssignedFieldValues() {
 | 
			
		||||
    ),
 | 
			
		||||
  };
 | 
			
		||||
  const actionType = fieldSchema['x-action'] ?? '';
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SchemaSettings.ActionModalItem
 | 
			
		||||
      title={t('Assign field values')}
 | 
			
		||||
      initialSchema={initialSchema}
 | 
			
		||||
      initialValues={fieldSchema?.['x-action-settings']?.assignedValues}
 | 
			
		||||
      modalTip={tips[actionType]}
 | 
			
		||||
      uid={fieldSchema?.['x-action-settings']?.schemaUid}
 | 
			
		||||
      onSubmit={(assignedValues) => {
 | 
			
		||||
  const onSubmit = useCallback(
 | 
			
		||||
    (assignedValues) => {
 | 
			
		||||
      fieldSchema['x-action-settings']['assignedValues'] = assignedValues;
 | 
			
		||||
      dn.emit('patch', {
 | 
			
		||||
        schema: {
 | 
			
		||||
@ -497,9 +492,23 @@ function AssignedFieldValues() {
 | 
			
		||||
          'x-action-settings': fieldSchema['x-action-settings'],
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
        dn.refresh();
 | 
			
		||||
      }}
 | 
			
		||||
    },
 | 
			
		||||
    [dn, fieldSchema],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FlagProvider isInAssignFieldValues={true}>
 | 
			
		||||
      <DefaultValueProvider isAllowToSetDefaultValue={() => false}>
 | 
			
		||||
        <SchemaSettings.ActionModalItem
 | 
			
		||||
          title={t('Assign field values')}
 | 
			
		||||
          initialSchema={initialSchema}
 | 
			
		||||
          initialValues={fieldSchema?.['x-action-settings']?.assignedValues}
 | 
			
		||||
          modalTip={tips[actionType]}
 | 
			
		||||
          uid={fieldSchema?.['x-action-settings']?.schemaUid}
 | 
			
		||||
          onSubmit={onSubmit}
 | 
			
		||||
        />
 | 
			
		||||
      </DefaultValueProvider>
 | 
			
		||||
    </FlagProvider>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -653,7 +662,7 @@ function FormWorkflowSelect(props) {
 | 
			
		||||
  const [workflowCollection, setWorkflowCollection] = useState(baseCollection.name);
 | 
			
		||||
  useFormEffects(() => {
 | 
			
		||||
    onFieldValueChange(`group[${index}].context`, (field) => {
 | 
			
		||||
      let collection = baseCollection;
 | 
			
		||||
      let collection: CollectionOptions = baseCollection;
 | 
			
		||||
      if (field.value) {
 | 
			
		||||
        const paths = field.value.split('.');
 | 
			
		||||
        for (let i = 0; i < paths.length && collection; i++) {
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,15 @@
 | 
			
		||||
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import { isPortalInBody } from '@nocobase/utils/client';
 | 
			
		||||
import { App, Button, Popover } from 'antd';
 | 
			
		||||
import classnames from 'classnames';
 | 
			
		||||
import lodash from 'lodash';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useActionContext } from '../..';
 | 
			
		||||
import { useDesignable } from '../../';
 | 
			
		||||
import { Icon } from '../../../icon';
 | 
			
		||||
import { useRecord } from '../../../record-provider';
 | 
			
		||||
import { useLocalVariables, useVariables } from '../../../variables';
 | 
			
		||||
import { SortableItem } from '../../common';
 | 
			
		||||
import { useCompile, useComponent, useDesigner } from '../../hooks';
 | 
			
		||||
import { useProps } from '../../hooks/useProps';
 | 
			
		||||
@ -51,44 +53,44 @@ export const Action: ComposedAction = observer(
 | 
			
		||||
    const values = useRecord();
 | 
			
		||||
    const designerProps = fieldSchema['x-designer-props'];
 | 
			
		||||
    const openMode = fieldSchema?.['x-component-props']?.['openMode'];
 | 
			
		||||
    const disabled = form.disabled || field.disabled || props.disabled;
 | 
			
		||||
    const disabled = form.disabled || field.disabled || field.data?.disabled || props.disabled;
 | 
			
		||||
    const openSize = fieldSchema?.['x-component-props']?.['openSize'];
 | 
			
		||||
    const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
 | 
			
		||||
    const { designable } = useDesignable();
 | 
			
		||||
    const tarComponent = useComponent(component) || component;
 | 
			
		||||
    const { modal } = App.useApp();
 | 
			
		||||
    const variables = useVariables();
 | 
			
		||||
    const localVariables = useLocalVariables({ currentForm: { values } as any });
 | 
			
		||||
 | 
			
		||||
    let actionTitle = title || compile(fieldSchema.title);
 | 
			
		||||
    actionTitle = lodash.isString(actionTitle) ? t(actionTitle) : actionTitle;
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      field.linkageProperty = {};
 | 
			
		||||
      linkageRules
 | 
			
		||||
        .filter((k) => !k.disabled)
 | 
			
		||||
        .forEach((v) => {
 | 
			
		||||
          return v.actions?.map((h) => {
 | 
			
		||||
            linkageAction(h.operator, field, v.condition, form.initialValues);
 | 
			
		||||
          v.actions?.forEach((h) => {
 | 
			
		||||
            linkageAction({
 | 
			
		||||
              operator: h.operator,
 | 
			
		||||
              field,
 | 
			
		||||
              condition: v.condition,
 | 
			
		||||
              values,
 | 
			
		||||
              variables,
 | 
			
		||||
              localVariables,
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
    }, [linkageRules, values, designable]);
 | 
			
		||||
        });
 | 
			
		||||
    }, [JSON.stringify(linkageRules), values, designable, field]);
 | 
			
		||||
 | 
			
		||||
    const renderButton = () => {
 | 
			
		||||
      if (!designable && field?.data?.hidden) {
 | 
			
		||||
        return null;
 | 
			
		||||
    const handleButtonClick = useCallback(
 | 
			
		||||
      (e: React.MouseEvent) => {
 | 
			
		||||
        if (isPortalInBody(e.target as Element)) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      return (
 | 
			
		||||
        <SortableItem
 | 
			
		||||
          data-testid={`${fieldSchema['x-action'] || fieldSchema.name}-action`}
 | 
			
		||||
          {...others}
 | 
			
		||||
          loading={field?.data?.loading}
 | 
			
		||||
          icon={icon ? <Icon type={icon} /> : null}
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
          style={{
 | 
			
		||||
            ...others.style,
 | 
			
		||||
            opacity: designable && field?.data?.hidden && 0.1,
 | 
			
		||||
          }}
 | 
			
		||||
          onClick={(e: React.MouseEvent) => {
 | 
			
		||||
            if (!disabled) {
 | 
			
		||||
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        if (!disabled) {
 | 
			
		||||
          const onOk = () => {
 | 
			
		||||
            onClick?.(e);
 | 
			
		||||
            setVisible(true);
 | 
			
		||||
@ -103,9 +105,33 @@ export const Action: ComposedAction = observer(
 | 
			
		||||
            onOk();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
          }}
 | 
			
		||||
      },
 | 
			
		||||
      [confirm, disabled, modal, onClick, run],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const buttonStyle = useMemo(() => {
 | 
			
		||||
      return {
 | 
			
		||||
        ...others.style,
 | 
			
		||||
        opacity: designable && field?.data?.hidden && 0.1,
 | 
			
		||||
      };
 | 
			
		||||
    }, [designable, field?.data?.hidden, others.style]);
 | 
			
		||||
 | 
			
		||||
    const renderButton = () => {
 | 
			
		||||
      if (!designable && field?.data?.hidden) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <SortableItem
 | 
			
		||||
          data-testid={`${fieldSchema['x-action'] || fieldSchema.name}-action`}
 | 
			
		||||
          {...others}
 | 
			
		||||
          loading={field?.data?.loading}
 | 
			
		||||
          icon={icon ? <Icon type={icon} /> : null}
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
          style={buttonStyle}
 | 
			
		||||
          onClick={handleButtonClick}
 | 
			
		||||
          component={tarComponent || Button}
 | 
			
		||||
          className={classnames(componentCls, hashId, className)}
 | 
			
		||||
          className={classnames(componentCls, hashId, className, 'nb-action')}
 | 
			
		||||
          type={props.type === 'danger' ? undefined : props.type}
 | 
			
		||||
        >
 | 
			
		||||
          {actionTitle}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import type { ISchema } from '@formily/react';
 | 
			
		||||
import { last } from 'lodash';
 | 
			
		||||
import { conditionAnalyse } from '../../common/utils/uitls';
 | 
			
		||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
 | 
			
		||||
import { VariableOption, VariablesContextType } from '../../../variables/types';
 | 
			
		||||
import { conditionAnalyses } from '../../common/utils/uitls';
 | 
			
		||||
const validateJSON = {
 | 
			
		||||
  validator: `{{(value, rule)=> {
 | 
			
		||||
    if (!value) {
 | 
			
		||||
@ -69,52 +69,69 @@ export const requestSettingsSchema: ISchema = {
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const linkageAction = (operator, field, condition, values) => {
 | 
			
		||||
  const disableResult = field?.linkageProperty?.disabled || [false];
 | 
			
		||||
  const displayResult = field?.linkageProperty?.display || ['visible'];
 | 
			
		||||
export const linkageAction = async ({
 | 
			
		||||
  operator,
 | 
			
		||||
  field,
 | 
			
		||||
  condition,
 | 
			
		||||
  values,
 | 
			
		||||
  variables,
 | 
			
		||||
  localVariables,
 | 
			
		||||
}: {
 | 
			
		||||
  operator;
 | 
			
		||||
  field;
 | 
			
		||||
  condition;
 | 
			
		||||
  values;
 | 
			
		||||
  variables: VariablesContextType;
 | 
			
		||||
  localVariables: VariableOption[];
 | 
			
		||||
}) => {
 | 
			
		||||
  field.data = field.data || {};
 | 
			
		||||
 | 
			
		||||
  switch (operator) {
 | 
			
		||||
    case ActionType.Visible:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        displayResult.push(operator);
 | 
			
		||||
        field.data = field.data || {};
 | 
			
		||||
        field.data.hidden = false;
 | 
			
		||||
      reset(field);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field._display = '_display' in field ? field._display : field.display;
 | 
			
		||||
        field.display = operator;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.display = field._display;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        display: displayResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.display = last(displayResult);
 | 
			
		||||
      break;
 | 
			
		||||
    case ActionType.Hidden:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        field.data = field.data || {};
 | 
			
		||||
      reset(field);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field.data.hidden = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.data = field.data || {};
 | 
			
		||||
        field.data.hidden = false;
 | 
			
		||||
      }
 | 
			
		||||
      break;
 | 
			
		||||
    case ActionType.Disabled:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        disableResult.push(true);
 | 
			
		||||
      reset(field);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field.data.disabled = true;
 | 
			
		||||
        field.disabled = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.data.disabled = false;
 | 
			
		||||
        field.disabled = false;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        disabled: disableResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.disabled = last(disableResult);
 | 
			
		||||
      break;
 | 
			
		||||
    case ActionType.Active:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        disableResult.push(false);
 | 
			
		||||
      reset(field);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field.data.disabled = false;
 | 
			
		||||
        field.disabled = false;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.data.disabled = true;
 | 
			
		||||
        field.disabled = true;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        disabled: disableResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.disabled = last(disableResult);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      return null;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function reset(field: any) {
 | 
			
		||||
  field.display = field._display;
 | 
			
		||||
  field.data.hidden = false;
 | 
			
		||||
  field.data.disabled = false;
 | 
			
		||||
  field.disabled = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import { isFunction } from 'mathjs';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { RecordProvider, useAPIClient } from '../../../';
 | 
			
		||||
import { isVariable } from '../../common/utils/uitls';
 | 
			
		||||
import { isVariable } from '../../../variables/utils/isVariable';
 | 
			
		||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
 | 
			
		||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
 | 
			
		||||
 | 
			
		||||
@ -110,3 +110,26 @@ interface AssociationSelectInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AssociationSelect = InternalAssociationSelect as unknown as AssociationSelectInterface;
 | 
			
		||||
 | 
			
		||||
export const AssociationSelectReadPretty = connect(
 | 
			
		||||
  (props: any) => {
 | 
			
		||||
    const service = useServiceOptions(props);
 | 
			
		||||
    if (props.fieldNames) {
 | 
			
		||||
      return <RemoteSelect.ReadPretty {...props} service={service}></RemoteSelect.ReadPretty>;
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  },
 | 
			
		||||
  mapProps(
 | 
			
		||||
    {
 | 
			
		||||
      dataSource: 'options',
 | 
			
		||||
      loading: true,
 | 
			
		||||
    },
 | 
			
		||||
    (props, field) => {
 | 
			
		||||
      return {
 | 
			
		||||
        ...props,
 | 
			
		||||
        fieldNames: props.fieldNames && { ...props.fieldNames, ...field.componentProps.fieldNames },
 | 
			
		||||
        suffixIcon: field?.['loading'] || field?.['validating'] ? <LoadingOutlined /> : props.suffixIcon,
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ -167,7 +167,7 @@ const InternalFileManager = (props) => {
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <RecordPickerProvider {...pickerProps}>
 | 
			
		||||
          <CollectionProvider name={collectionField.target}>
 | 
			
		||||
          <CollectionProvider name={collectionField?.target}>
 | 
			
		||||
            <FormProvider>
 | 
			
		||||
              <TableSelectorParamsProvider params={{ filter: getFilter() }}>
 | 
			
		||||
                <SchemaComponentOptions scope={{ usePickActionProps, useTableSelectorProps }}>
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
import { observer, useField, connect, createSchemaField, FormProvider, useFieldSchema, Field } from '@formily/react';
 | 
			
		||||
import { createForm, onFormValuesChange } from '@formily/core';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { Space, Tag, Spin, Select as AntdSelect, Input } from 'antd';
 | 
			
		||||
import { ArrayItems, FormItem } from '@formily/antd-v5';
 | 
			
		||||
import React, { useState, useCallback, useEffect, useMemo } from 'react';
 | 
			
		||||
import { createForm, onFormValuesChange } from '@formily/core';
 | 
			
		||||
import { FormProvider, connect, createSchemaField, observer, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { Select as AntdSelect, Input, Space, Spin, Tag } from 'antd';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCompile, SchemaComponent } from '../../../schema-component';
 | 
			
		||||
import { useAPIClient, useCollectionManager } from '../../../';
 | 
			
		||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
 | 
			
		||||
import { SchemaComponent, useCompile } from '../../../schema-component';
 | 
			
		||||
import useServiceOptions, { useAssociationFieldContext } from './hooks';
 | 
			
		||||
 | 
			
		||||
const EMPTY = 'N/A';
 | 
			
		||||
@ -73,7 +73,7 @@ const CascadeSelect = connect((props) => {
 | 
			
		||||
              if (Array.isArray(label)) {
 | 
			
		||||
                label = label
 | 
			
		||||
                  .map((item, index) => {
 | 
			
		||||
                    const option = targetField.uiSchema.enum.find((i) => i.value === item);
 | 
			
		||||
                    const option = (targetField.uiSchema.enum as any).find?.((i) => i.value === item);
 | 
			
		||||
                    if (option) {
 | 
			
		||||
                      return (
 | 
			
		||||
                        <Tag key={index} color={option.color} style={{ marginRight: 3 }}>
 | 
			
		||||
@ -86,7 +86,7 @@ const CascadeSelect = connect((props) => {
 | 
			
		||||
                  })
 | 
			
		||||
                  .reverse();
 | 
			
		||||
              } else {
 | 
			
		||||
                const item = targetField.uiSchema.enum.find((i) => i.value === label);
 | 
			
		||||
                const item = (targetField.uiSchema.enum as any).find?.((i) => i.value === label);
 | 
			
		||||
                if (item) {
 | 
			
		||||
                  label = <Tag color={item.color}>{item.label}</Tag>;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import { RecursionField, SchemaOptionsContext, observer, useField, useFieldSchem
 | 
			
		||||
import React, { useEffect } from 'react';
 | 
			
		||||
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
 | 
			
		||||
import { CollectionProvider } from '../../../collection-manager';
 | 
			
		||||
import { useSchemaOptionsContext } from '../../../schema-component';
 | 
			
		||||
import { FormItem, useSchemaOptionsContext } from '../../../schema-component';
 | 
			
		||||
import Select from '../select/Select';
 | 
			
		||||
import { useAssociationFieldContext, useInsertSchema } from './hooks';
 | 
			
		||||
import schema from './schema';
 | 
			
		||||
@ -24,8 +24,9 @@ export const InternalSubTable = observer(
 | 
			
		||||
    const option = useSchemaOptionsContext();
 | 
			
		||||
    const components = {
 | 
			
		||||
      ...option.components,
 | 
			
		||||
      'Radio.Group': (props) => <Select data-testid="antd-select" {...props} />,
 | 
			
		||||
      'Checkbox.Group': (props) => <Select data-testid="antd-select" multiple={true} mode="multiple" {...props} />,
 | 
			
		||||
      FormItem,
 | 
			
		||||
      'Radio.Group': Select,
 | 
			
		||||
      'Checkbox.Group': (props) => <Select multiple={true} mode="multiple" {...props} />,
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
      <CollectionProvider name={options.target}>
 | 
			
		||||
 | 
			
		||||
@ -57,7 +57,7 @@ export const ReadPrettyInternalViewer: React.FC = observer(
 | 
			
		||||
        );
 | 
			
		||||
        const text = getLabelFormatValue(compile(labelUiSchema), val, true);
 | 
			
		||||
        return (
 | 
			
		||||
          <Fragment key={`${record.id}_${index}`}>
 | 
			
		||||
          <Fragment key={`${record?.id}_${index}`}>
 | 
			
		||||
            <span>
 | 
			
		||||
              {snapshot ? (
 | 
			
		||||
                text
 | 
			
		||||
 | 
			
		||||
@ -2,15 +2,23 @@ import { CloseOutlined, PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { ArrayField } from '@formily/core';
 | 
			
		||||
import { spliceArrayState } from '@formily/core/esm/shared/internals';
 | 
			
		||||
import { observer, RecursionField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { RecursionField, observer, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { action } from '@formily/reactive';
 | 
			
		||||
import { each } from '@formily/shared';
 | 
			
		||||
import { Button, Card, Divider, Tooltip } from 'antd';
 | 
			
		||||
import React, { useContext } from 'react';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import React, { useCallback, useContext } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { FormActiveFieldsProvider } from '../../../block-provider';
 | 
			
		||||
import { RecordIndexProvider, RecordProvider, useRecord } from '../../../record-provider';
 | 
			
		||||
import { isPatternDisabled, isSystemField } from '../../../schema-settings';
 | 
			
		||||
import {
 | 
			
		||||
  DefaultValueProvider,
 | 
			
		||||
  interfacesOfUnsupportedDefaultValue,
 | 
			
		||||
} from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
 | 
			
		||||
import { LocalVariablesProvider } from '../../../variables/hooks/useLocalVariables';
 | 
			
		||||
import { AssociationFieldContext } from './context';
 | 
			
		||||
import { useAssociationFieldContext } from './hooks';
 | 
			
		||||
import { RecordProvider, useRecord } from '../../../record-provider';
 | 
			
		||||
 | 
			
		||||
export const Nester = (props) => {
 | 
			
		||||
  const { options } = useContext(AssociationFieldContext);
 | 
			
		||||
@ -24,7 +32,46 @@ export const Nester = (props) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ToOneNester = (props) => {
 | 
			
		||||
  return <Card bordered={true}>{props.children}</Card>;
 | 
			
		||||
  const { field } = useAssociationFieldContext<ArrayField>();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
 | 
			
		||||
  // 保存原始数据,用于判断是否允许设置默认值
 | 
			
		||||
  const recordRef = React.useRef(_.omit(record, '__parent'));
 | 
			
		||||
 | 
			
		||||
  const isAllowToSetDefaultValue = useCallback(({ form, fieldSchema, collectionField, getInterface }) => {
 | 
			
		||||
    // 当 Field component 不是下列组件时,不允许设置默认值
 | 
			
		||||
    if (
 | 
			
		||||
      collectionField.target &&
 | 
			
		||||
      fieldSchema['x-component-props']?.mode &&
 | 
			
		||||
      !['Picker', 'Select'].includes(fieldSchema['x-component-props'].mode)
 | 
			
		||||
    ) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // hasOne 和 belongsTo 类型的字段只能有一个值,不会新增值,所以在编辑状态下不允许设置默认值
 | 
			
		||||
    if (!_.isEmpty(recordRef.current)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      !form?.readPretty &&
 | 
			
		||||
      !isPatternDisabled(fieldSchema) &&
 | 
			
		||||
      !interfacesOfUnsupportedDefaultValue.includes(collectionField?.interface) &&
 | 
			
		||||
      !isSystemField(collectionField, getInterface)
 | 
			
		||||
    );
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <FormActiveFieldsProvider name="nester">
 | 
			
		||||
      <RecordProvider record={field.value}>
 | 
			
		||||
        <LocalVariablesProvider iterationCtx={field.value}>
 | 
			
		||||
          <DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}>
 | 
			
		||||
            <Card bordered={true}>{props.children}</Card>
 | 
			
		||||
          </DefaultValueProvider>
 | 
			
		||||
        </LocalVariablesProvider>
 | 
			
		||||
      </RecordProvider>
 | 
			
		||||
    </FormActiveFieldsProvider>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ToManyNester = observer(
 | 
			
		||||
@ -32,7 +79,30 @@ const ToManyNester = observer(
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>();
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    return (field.value || []).length > 0 ? (
 | 
			
		||||
 | 
			
		||||
    if (!Array.isArray(field.value)) {
 | 
			
		||||
      field.value = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const isAllowToSetDefaultValue = useCallback(({ form, fieldSchema, collectionField, getInterface }) => {
 | 
			
		||||
      // 当 Field component 不是下列组件时,不允许设置默认值
 | 
			
		||||
      if (
 | 
			
		||||
        collectionField.target &&
 | 
			
		||||
        fieldSchema['x-component-props']?.mode &&
 | 
			
		||||
        !['Picker', 'Select'].includes(fieldSchema['x-component-props'].mode)
 | 
			
		||||
      ) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        !form?.readPretty &&
 | 
			
		||||
        !isPatternDisabled(fieldSchema) &&
 | 
			
		||||
        !interfacesOfUnsupportedDefaultValue.includes(collectionField?.interface) &&
 | 
			
		||||
        !isSystemField(collectionField, getInterface)
 | 
			
		||||
      );
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    return field.value.length > 0 ? (
 | 
			
		||||
      <Card
 | 
			
		||||
        bordered={true}
 | 
			
		||||
        style={{ position: 'relative' }}
 | 
			
		||||
@ -42,7 +112,7 @@ const ToManyNester = observer(
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
      >
 | 
			
		||||
        {(field.value || []).map((value, index) => {
 | 
			
		||||
        {field.value.map((value, index) => {
 | 
			
		||||
          let allowed = allowDissociate;
 | 
			
		||||
          if (!allowDissociate) {
 | 
			
		||||
            allowed = !value?.[options.targetKey];
 | 
			
		||||
@ -55,7 +125,7 @@ const ToManyNester = observer(
 | 
			
		||||
                    <PlusOutlined
 | 
			
		||||
                      style={{ zIndex: 1000, marginRight: '10px', color: '#a8a3a3' }}
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        action(() => {
 | 
			
		||||
                        void action(() => {
 | 
			
		||||
                          if (!Array.isArray(field.value)) {
 | 
			
		||||
                            field.value = [];
 | 
			
		||||
                          }
 | 
			
		||||
@ -80,7 +150,7 @@ const ToManyNester = observer(
 | 
			
		||||
                    <CloseOutlined
 | 
			
		||||
                      style={{ zIndex: 1000, color: '#a8a3a3' }}
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        action(() => {
 | 
			
		||||
                        void action(() => {
 | 
			
		||||
                          spliceArrayState(field as any, {
 | 
			
		||||
                            startIndex: index,
 | 
			
		||||
                            deleteCount: 1,
 | 
			
		||||
@ -93,9 +163,21 @@ const ToManyNester = observer(
 | 
			
		||||
                  </Tooltip>
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
              <FormActiveFieldsProvider name="nester">
 | 
			
		||||
                <RecordProvider record={value}>
 | 
			
		||||
                <RecursionField onlyRenderProperties basePath={field.address.concat(index)} schema={fieldSchema} />
 | 
			
		||||
                  <LocalVariablesProvider iterationCtx={value}>
 | 
			
		||||
                    <RecordIndexProvider index={index}>
 | 
			
		||||
                      <DefaultValueProvider isAllowToSetDefaultValue={isAllowToSetDefaultValue}>
 | 
			
		||||
                        <RecursionField
 | 
			
		||||
                          onlyRenderProperties
 | 
			
		||||
                          basePath={field.address.concat(index)}
 | 
			
		||||
                          schema={fieldSchema}
 | 
			
		||||
                        />
 | 
			
		||||
                      </DefaultValueProvider>
 | 
			
		||||
                    </RecordIndexProvider>
 | 
			
		||||
                  </LocalVariablesProvider>
 | 
			
		||||
                </RecordProvider>
 | 
			
		||||
              </FormActiveFieldsProvider>
 | 
			
		||||
              <Divider />
 | 
			
		||||
            </React.Fragment>
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
@ -7,12 +7,15 @@ import { action } from '@formily/reactive';
 | 
			
		||||
import { isArr } from '@formily/shared';
 | 
			
		||||
import { Button } from 'antd';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { FormActiveFieldsProvider } from '../../../block-provider';
 | 
			
		||||
import { useSubTableSpecialCase } from '../form-item/hooks/useSpecialCase';
 | 
			
		||||
import { Table } from '../table-v2/Table';
 | 
			
		||||
import { useAssociationFieldContext } from './hooks';
 | 
			
		||||
 | 
			
		||||
export const SubTable: any = observer(
 | 
			
		||||
  (props: any) => {
 | 
			
		||||
    const { field } = useAssociationFieldContext<ArrayField>();
 | 
			
		||||
    useSubTableSpecialCase({ field });
 | 
			
		||||
    const move = (fromIndex: number, toIndex: number) => {
 | 
			
		||||
      if (toIndex === undefined) return;
 | 
			
		||||
      if (!isArr(field.value)) return;
 | 
			
		||||
@ -59,6 +62,7 @@ export const SubTable: any = observer(
 | 
			
		||||
          }
 | 
			
		||||
        `}
 | 
			
		||||
      >
 | 
			
		||||
        <FormActiveFieldsProvider name="nester">
 | 
			
		||||
          <Table
 | 
			
		||||
            className={css`
 | 
			
		||||
              .ant-formily-item.ant-formily-item-feedback-layout-loose {
 | 
			
		||||
@ -87,7 +91,7 @@ export const SubTable: any = observer(
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    field.value = field.value || [];
 | 
			
		||||
                    field.value.push({});
 | 
			
		||||
                  field.onInput(field.value);
 | 
			
		||||
                    void field.onInput(field.value);
 | 
			
		||||
                  }}
 | 
			
		||||
                  icon={<PlusOutlined />}
 | 
			
		||||
                >
 | 
			
		||||
@ -97,6 +101,7 @@ export const SubTable: any = observer(
 | 
			
		||||
            }
 | 
			
		||||
            isSubTable={true}
 | 
			
		||||
          />
 | 
			
		||||
        </FormActiveFieldsProvider>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,22 @@
 | 
			
		||||
import { GeneralField } from '@formily/core';
 | 
			
		||||
import { useField, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import flat from 'flat';
 | 
			
		||||
import { isString } from 'lodash';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { reaction } from '@formily/reactive';
 | 
			
		||||
import { flatten } from '@nocobase/utils/client';
 | 
			
		||||
import _, { isString } from 'lodash';
 | 
			
		||||
import cloneDeep from 'lodash/cloneDeep';
 | 
			
		||||
import { useCallback, useContext, useMemo } from 'react';
 | 
			
		||||
import { useBlockRequestContext } from '../../../block-provider/BlockProvider';
 | 
			
		||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { isInFilterFormBlock } from '../../../filter-provider';
 | 
			
		||||
import { useRecord } from '../../../record-provider';
 | 
			
		||||
import { getInnermostKeyAndValue } from '../../common/utils/uitls';
 | 
			
		||||
import { useParseDataScopeFilter } from '../../../schema-settings';
 | 
			
		||||
import { DEBOUNCE_WAIT } from '../../../variables';
 | 
			
		||||
import { getPath } from '../../../variables/utils/getPath';
 | 
			
		||||
import { isVariable } from '../../../variables/utils/isVariable';
 | 
			
		||||
import { useDesignable } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { AssociationFieldContext } from './context';
 | 
			
		||||
import { extractFilterfield, extractValuesByPattern, generatePattern, parseVariables } from './util';
 | 
			
		||||
 | 
			
		||||
export const useInsertSchema = (component) => {
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
@ -49,74 +53,47 @@ export default function useServiceOptions(props) {
 | 
			
		||||
  const params = service?.params || {};
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  const ctx = useBlockRequestContext();
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
  const { getCollectionFields, getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
  const { getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const parseFilter = useCallback(
 | 
			
		||||
    (rules) => {
 | 
			
		||||
      if (!rules) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
      }
 | 
			
		||||
      if (typeof rules === 'string') {
 | 
			
		||||
        return rules;
 | 
			
		||||
      }
 | 
			
		||||
      const type = Object.keys(rules)[0] || '$and';
 | 
			
		||||
      const conditions = rules[type];
 | 
			
		||||
      const results = [];
 | 
			
		||||
      conditions?.forEach((c) => {
 | 
			
		||||
        const jsonlogic = getInnermostKeyAndValue(c);
 | 
			
		||||
        const regex = /{{(.*?)}}/;
 | 
			
		||||
        const matches = jsonlogic.value?.match?.(regex);
 | 
			
		||||
        if (!matches || (!matches[1].includes('$form') && !matches[1].includes('$iteration'))) {
 | 
			
		||||
          results.push(c);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        const associationfield = extractFilterfield(matches[1]);
 | 
			
		||||
        const filterCollectionField = getCollectionJoinField(`${ctx.props.collection}.${associationfield}`);
 | 
			
		||||
        if (['o2m', 'm2m'].includes(filterCollectionField?.interface)) {
 | 
			
		||||
          // 对多子表单
 | 
			
		||||
          const pattern = generatePattern(matches?.[1], associationfield);
 | 
			
		||||
          const parseValue: any = extractValuesByPattern(flat(form.values), pattern);
 | 
			
		||||
          const filters = parseValue.map((v) => {
 | 
			
		||||
            return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
 | 
			
		||||
          });
 | 
			
		||||
          results.push({ $or: filters });
 | 
			
		||||
        } else {
 | 
			
		||||
          const variablesCtx = { $form: form.values, $iteration: form.values };
 | 
			
		||||
          let str = matches?.[1];
 | 
			
		||||
          if (str.includes('$iteration')) {
 | 
			
		||||
            const path = field.path.segments.concat([]);
 | 
			
		||||
            path.pop();
 | 
			
		||||
            str = str.replace('$iteration.', `$iteration.${path.join('.')}.`);
 | 
			
		||||
          }
 | 
			
		||||
          const parseValue = parseVariables(str, variablesCtx);
 | 
			
		||||
          if (Array.isArray(parseValue)) {
 | 
			
		||||
            const filters = parseValue.map((v) => {
 | 
			
		||||
              return JSON.parse(JSON.stringify(c).replace(jsonlogic.value, v));
 | 
			
		||||
            });
 | 
			
		||||
            results.push({ $or: filters });
 | 
			
		||||
          } else {
 | 
			
		||||
            const filterObj = JSON.parse(
 | 
			
		||||
              JSON.stringify(c).replace(jsonlogic.value, str.endsWith('id') ? parseValue ?? 0 : parseValue),
 | 
			
		||||
            );
 | 
			
		||||
            results.push(filterObj);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      return { [type]: results };
 | 
			
		||||
    },
 | 
			
		||||
    [ctx.props?.collection, field.path.segments, form.values, getCollectionJoinField],
 | 
			
		||||
  );
 | 
			
		||||
  const { parseFilter } = useParseDataScopeFilter();
 | 
			
		||||
  const [fieldServiceFilter, setFieldServiceFilter] = useState(null);
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
 | 
			
		||||
  const fieldServiceFilter = useMemo(() => {
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const filterFromSchema = isString(fieldSchema?.['x-component-props']?.service?.params?.filter)
 | 
			
		||||
      ? field.componentProps?.service?.params?.filter
 | 
			
		||||
      : fieldSchema?.['x-component-props']?.service?.params?.filter;
 | 
			
		||||
 | 
			
		||||
    return mergeFilter([parseFilter(filterFromSchema) || service?.params?.filter]);
 | 
			
		||||
  }, [field.componentProps?.service?.params?.filter, fieldSchema, parseFilter, service?.params?.filter]);
 | 
			
		||||
    const _run = async () => {
 | 
			
		||||
      const result = await parseFilter(mergeFilter([filterFromSchema || service?.params?.filter]));
 | 
			
		||||
      setFieldServiceFilter(removeNullCondition(result));
 | 
			
		||||
    };
 | 
			
		||||
    const run = _.debounce(_run, DEBOUNCE_WAIT);
 | 
			
		||||
 | 
			
		||||
    _run();
 | 
			
		||||
 | 
			
		||||
    reaction(() => {
 | 
			
		||||
      // 这一步主要是为了使 reaction 能够收集到依赖
 | 
			
		||||
      const flat = flatten(filterFromSchema, {
 | 
			
		||||
        breakOn({ key }) {
 | 
			
		||||
          return key.startsWith('$') && key !== '$and' && key !== '$or';
 | 
			
		||||
        },
 | 
			
		||||
        transformValue(value) {
 | 
			
		||||
          if (!isVariable(value)) {
 | 
			
		||||
            return value;
 | 
			
		||||
          }
 | 
			
		||||
          const result = _.get({ $nForm: form?.values }, getPath(value));
 | 
			
		||||
          return result;
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      return flat;
 | 
			
		||||
    }, run);
 | 
			
		||||
  }, [
 | 
			
		||||
    field.componentProps?.service?.params?.filter,
 | 
			
		||||
    fieldSchema?.['x-component-props']?.service?.params?.filter,
 | 
			
		||||
    service?.params?.filter,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const normalizeValues = useCallback(
 | 
			
		||||
    (obj) => {
 | 
			
		||||
@ -132,16 +109,21 @@ export default function useServiceOptions(props) {
 | 
			
		||||
    if (props.value === undefined || props.value === null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let result: any[] = null;
 | 
			
		||||
 | 
			
		||||
    if (Array.isArray(props.value)) {
 | 
			
		||||
      return props.value.map(normalizeValues);
 | 
			
		||||
      result = props.value.map(normalizeValues);
 | 
			
		||||
    } else {
 | 
			
		||||
      return [normalizeValues(props.value)];
 | 
			
		||||
      result = [normalizeValues(props.value)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result.filter(Boolean);
 | 
			
		||||
  }, [props.value, normalizeValues]);
 | 
			
		||||
 | 
			
		||||
  const collectionField = useMemo(() => {
 | 
			
		||||
    return getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
 | 
			
		||||
  }, [fieldSchema.name]);
 | 
			
		||||
  }, [fieldSchema]);
 | 
			
		||||
 | 
			
		||||
  const sourceValue = record?.[collectionField?.sourceKey];
 | 
			
		||||
  const filter = useMemo(() => {
 | 
			
		||||
@ -169,7 +151,7 @@ export default function useServiceOptions(props) {
 | 
			
		||||
              },
 | 
			
		||||
            }
 | 
			
		||||
          : null,
 | 
			
		||||
        params?.filter && value
 | 
			
		||||
        params?.filter && value?.length
 | 
			
		||||
          ? {
 | 
			
		||||
              [fieldNames?.value]: {
 | 
			
		||||
                ['$in']: value,
 | 
			
		||||
@ -179,7 +161,16 @@ export default function useServiceOptions(props) {
 | 
			
		||||
      ],
 | 
			
		||||
      '$or',
 | 
			
		||||
    );
 | 
			
		||||
  }, [params?.filter, getCollectionFields, collectionField, sourceValue, value, fieldNames?.value]);
 | 
			
		||||
  }, [
 | 
			
		||||
    collectionField?.interface,
 | 
			
		||||
    collectionField.foreignKey,
 | 
			
		||||
    fieldSchema,
 | 
			
		||||
    fieldServiceFilter,
 | 
			
		||||
    sourceValue,
 | 
			
		||||
    params?.filter,
 | 
			
		||||
    value,
 | 
			
		||||
    fieldNames?.value,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  return useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import { ISchema } from '@formily/react';
 | 
			
		||||
import { ISchema, Schema } from '@formily/react';
 | 
			
		||||
import { isArr } from '@formily/shared';
 | 
			
		||||
import { getDefaultFormat, str2moment } from '@nocobase/utils/client';
 | 
			
		||||
import { Tag } from 'antd';
 | 
			
		||||
import { get, isFunction } from 'lodash';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { CollectionFieldOptions, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
@ -88,43 +87,9 @@ export function isShowFilePicker(labelUiSchema) {
 | 
			
		||||
  return labelUiSchema?.['x-component'] === 'Preview';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const toValue = (value, placeholder) => {
 | 
			
		||||
  if (value === null || value === undefined) {
 | 
			
		||||
    return placeholder;
 | 
			
		||||
  }
 | 
			
		||||
  return value;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const parseVariables = (str: string, ctx) => {
 | 
			
		||||
  if (str) {
 | 
			
		||||
    const result = get(ctx, str);
 | 
			
		||||
    return isFunction(result) ? result() : result;
 | 
			
		||||
  } else {
 | 
			
		||||
    return str;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
export function extractFilterfield(str) {
 | 
			
		||||
  const match = str.match(/^\$form\.([^.[\]]+)/);
 | 
			
		||||
  if (match) {
 | 
			
		||||
    return match[1];
 | 
			
		||||
  }
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function extractValuesByPattern(obj, pattern) {
 | 
			
		||||
  const regexPattern = new RegExp(pattern.replace(/\*/g, '\\d+'));
 | 
			
		||||
  const result = [];
 | 
			
		||||
 | 
			
		||||
  for (const key in obj) {
 | 
			
		||||
    if (regexPattern.test(key)) {
 | 
			
		||||
      const value = obj[key];
 | 
			
		||||
      result.push(value);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
export function generatePattern(str, fieldName) {
 | 
			
		||||
  const result = str.replace(`$form.${fieldName}.`, `${fieldName}.*.`);
 | 
			
		||||
  return result;
 | 
			
		||||
/**
 | 
			
		||||
 * 当前字段的模式是否是 `子表格` 或者 `子表单`
 | 
			
		||||
 */
 | 
			
		||||
export function isSubMode(fieldSchema: Schema) {
 | 
			
		||||
  return ['Nester', 'SubTable', 'PopoverNester'].includes(fieldSchema['x-component-props']?.mode);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,26 +2,21 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import {
 | 
			
		||||
  useCollection,
 | 
			
		||||
  useCollectionFilterOptions,
 | 
			
		||||
  useCollectionManager,
 | 
			
		||||
  useSortFields,
 | 
			
		||||
} from '../../../collection-manager';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollection, useCollectionManager, useSortFields } from '../../../collection-manager';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useCompile, useDesignable } from '../../hooks';
 | 
			
		||||
 | 
			
		||||
export const AssociationFilterItemDesigner = (props) => {
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
 | 
			
		||||
  const collectionField = getField(fieldSchema['name']!) || getCollectionJoinField(fieldSchema['x-collection-field']);
 | 
			
		||||
  const filterEnum = collectionField?.target ? useCollectionFilterOptions(collectionField?.target) : [];
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-component-props']?.params?.filter || {};
 | 
			
		||||
  const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
 | 
			
		||||
 | 
			
		||||
  const { getCollectionFields } = useCollectionManager();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
@ -30,9 +25,6 @@ export const AssociationFilterItemDesigner = (props) => {
 | 
			
		||||
  const targetFields = collectionField?.target ? getCollectionFields(collectionField?.target) : [];
 | 
			
		||||
 | 
			
		||||
  const options = targetFields
 | 
			
		||||
    // .filter(
 | 
			
		||||
    //   (field) => field?.interface && ['id', 'input', 'phone', 'email', 'integer', 'number'].includes(field?.interface),
 | 
			
		||||
    // )
 | 
			
		||||
    .filter((field) => !field?.target && field.type !== 'boolean')
 | 
			
		||||
    .map((field) => ({
 | 
			
		||||
      value: field?.name,
 | 
			
		||||
@ -117,22 +109,10 @@ export const AssociationFilterItemDesigner = (props) => {
 | 
			
		||||
          dn.refresh();
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                enum: filterEnum,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {},
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={collectionField?.target}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-component-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          _.set(field.componentProps, 'params', {
 | 
			
		||||
            ...field.componentProps?.params,
 | 
			
		||||
 | 
			
		||||
@ -7,20 +7,15 @@ import _ from 'lodash';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useFilterByTk, useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import {
 | 
			
		||||
  useCollection,
 | 
			
		||||
  useCollectionFilterOptions,
 | 
			
		||||
  useCollectionManager,
 | 
			
		||||
  useSortFields,
 | 
			
		||||
} from '../../../collection-manager';
 | 
			
		||||
import { useCollection, useCollectionManager, useSortFields } from '../../../collection-manager';
 | 
			
		||||
import { GeneralSchemaItems } from '../../../schema-items';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings';
 | 
			
		||||
import useIsAllowToSetDefaultValue from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
 | 
			
		||||
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
 | 
			
		||||
import { useCompile, useDesignable, useFieldComponentOptions, useFieldTitle } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { RemoteSelect, RemoteSelectProps } from '../remote-select';
 | 
			
		||||
import { defaultFieldNames } from '../select';
 | 
			
		||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
 | 
			
		||||
import { ReadPretty } from './ReadPretty';
 | 
			
		||||
import useServiceOptions from './useServiceOptions';
 | 
			
		||||
 | 
			
		||||
@ -105,6 +100,7 @@ AssociationSelect.Designer = function Designer() {
 | 
			
		||||
  const { dn, refresh, insertAdjacent } = useDesignable();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const IsShowMultipleSwitch = useIsShowMultipleSwitch();
 | 
			
		||||
  const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue();
 | 
			
		||||
 | 
			
		||||
  const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
 | 
			
		||||
  const fieldComponentOptions = useFieldComponentOptions();
 | 
			
		||||
@ -119,8 +115,6 @@ AssociationSelect.Designer = function Designer() {
 | 
			
		||||
  const sortFields = useSortFields(collectionField?.target);
 | 
			
		||||
 | 
			
		||||
  const defaultSort = field.componentProps?.service?.params?.sort || [];
 | 
			
		||||
  const defaultFilter = field.componentProps?.service?.params?.filter || {};
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(collectionField?.target);
 | 
			
		||||
 | 
			
		||||
  // TODO: 这里 fieldSchema['x-read-pretty'] 的值为 true,但是 field.readPretty 的值却为 false,不知道什么原因
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
@ -287,10 +281,7 @@ AssociationSelect.Designer = function Designer() {
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {form &&
 | 
			
		||||
        !form?.readPretty &&
 | 
			
		||||
        isShowDefaultValue(collectionField, getInterface) &&
 | 
			
		||||
        !isPatternDisabled(fieldSchema) && (
 | 
			
		||||
      {isAllowToSetDefaultValue() && (
 | 
			
		||||
        <SchemaSettings.ModalItem
 | 
			
		||||
          title={t('Set default value')}
 | 
			
		||||
          components={{ ArrayCollapse, FormLayout }}
 | 
			
		||||
@ -409,25 +400,10 @@ AssociationSelect.Designer = function Designer() {
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      ) : null}
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                // title: '数据范围',
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {
 | 
			
		||||
                  dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={collectionField?.target}
 | 
			
		||||
        defaultFilter={field.componentProps?.service?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          filter = removeNullCondition(filter);
 | 
			
		||||
          _.set(field.componentProps, 'service.params.filter', filter);
 | 
			
		||||
@ -646,8 +622,6 @@ AssociationSelect.FilterDesigner = function FilterDesigner() {
 | 
			
		||||
  const sortFields = useSortFields(collectionField?.target);
 | 
			
		||||
 | 
			
		||||
  const defaultSort = field.componentProps?.service?.params?.sort || [];
 | 
			
		||||
  const defaultFilter = field.componentProps?.service?.params?.filter || {};
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(collectionField?.target);
 | 
			
		||||
 | 
			
		||||
  const sort = defaultSort?.map((item: string) => {
 | 
			
		||||
    return item.startsWith('-')
 | 
			
		||||
@ -839,25 +813,10 @@ AssociationSelect.FilterDesigner = function FilterDesigner() {
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                // title: '数据范围',
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {
 | 
			
		||||
                  dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={collectionField?.target}
 | 
			
		||||
        defaultFilter={field.componentProps?.service?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          filter = removeNullCondition(filter);
 | 
			
		||||
          _.set(field.componentProps, 'service.params.filter', filter);
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,22 @@
 | 
			
		||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { FixedBlockDesignerItem, removeNullCondition, useDesignable } from '../..';
 | 
			
		||||
import { useCalendarBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCalendarBlockContext, useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
 | 
			
		||||
 | 
			
		||||
export const CalendarDesigner = () => {
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const { name, title } = useCollection();
 | 
			
		||||
  const { getCollectionFieldsOptions } = useCollectionManager();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { service } = useCalendarBlockContext();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const template = useSchemaTemplate();
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
 | 
			
		||||
  const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
 | 
			
		||||
 | 
			
		||||
@ -105,30 +102,10 @@ export const CalendarDesigner = () => {
 | 
			
		||||
          dn.refresh();
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {
 | 
			
		||||
                  dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
        initialValues={
 | 
			
		||||
          {
 | 
			
		||||
            // title: field.title,
 | 
			
		||||
            // icon: field.componentProps.icon,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={name}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          filter = removeNullCondition(filter);
 | 
			
		||||
          const params = field.decoratorProps.params || {};
 | 
			
		||||
 | 
			
		||||
@ -56,17 +56,20 @@ export const Cascader = connect(
 | 
			
		||||
            if (selectedOptions[index]) {
 | 
			
		||||
              return <span key={label}>{label}</span>;
 | 
			
		||||
            }
 | 
			
		||||
            const item = toArr(value).find((item) => item[fieldNames.value] === label);
 | 
			
		||||
            const item = toArr(value)
 | 
			
		||||
              .filter(Boolean)
 | 
			
		||||
              .find((item) => item[fieldNames.value] === label);
 | 
			
		||||
            return <span key={label}>{item?.[fieldNames.label] || label}</span>;
 | 
			
		||||
          })}
 | 
			
		||||
        </Space>
 | 
			
		||||
      );
 | 
			
		||||
    };
 | 
			
		||||
    const handelDropDownVisible = (value) => {
 | 
			
		||||
      if (value && !field.dataSource.length) {
 | 
			
		||||
      if (value && !field.dataSource?.length) {
 | 
			
		||||
        run();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <AntdCascader
 | 
			
		||||
        loading={loading}
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ function useOptions({ filter, isTableOid }: CollectionSelectProps) {
 | 
			
		||||
    : collections;
 | 
			
		||||
  const filtered =
 | 
			
		||||
    typeof filter === 'function'
 | 
			
		||||
      ? (inheritCollections || currentCollections).filter(filter)
 | 
			
		||||
      ? ((inheritCollections || currentCollections) as any[]).filter(filter)
 | 
			
		||||
      : inheritCollections || currentCollections;
 | 
			
		||||
  return filtered
 | 
			
		||||
    .filter((item) => !item.hidden)
 | 
			
		||||
 | 
			
		||||
@ -37,8 +37,9 @@ const InternalRangePicker = connect(
 | 
			
		||||
 | 
			
		||||
export const DatePicker = (props) => {
 | 
			
		||||
  const { utc = true } = useDatePickerContext();
 | 
			
		||||
  const value = Array.isArray(props.value) ? props.value[0] : props.value;
 | 
			
		||||
  props = { utc, ...props };
 | 
			
		||||
  return <InternalDatePicker {...props} />;
 | 
			
		||||
  return <InternalDatePicker {...props} value={value} />;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DatePicker.ReadPretty = ReadPretty.DatePicker;
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,18 @@ describe('getDateRanges', () => {
 | 
			
		||||
    expect(end.toISOString()).toBe(dayjs().endOf('day').toISOString());
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('yesterday', () => {
 | 
			
		||||
    const [start, end] = dateRanges.yesterday();
 | 
			
		||||
    expect(dayjs(start).isSame(dayjs().subtract(1, 'day'), 'day')).toBe(true);
 | 
			
		||||
    expect(dayjs(end).isSame(dayjs().subtract(1, 'day'), 'day')).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('tomorrow', () => {
 | 
			
		||||
    const [start, end] = dateRanges.tomorrow();
 | 
			
		||||
    expect(dayjs(start).isSame(dayjs().add(1, 'day'), 'day')).toBe(true);
 | 
			
		||||
    expect(dayjs(end).isSame(dayjs().add(1, 'day'), 'day')).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('lastWeek', () => {
 | 
			
		||||
    const [start, end] = dateRanges.lastWeek();
 | 
			
		||||
    expect(start.toISOString()).toBe(dayjs().add(-1, 'week').startOf('isoWeek').toISOString());
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,9 @@ const toStringByPicker = (value, picker, timezone: 'gmt' | 'local') => {
 | 
			
		||||
  if (!dayjs.isDayjs(value)) return value;
 | 
			
		||||
  if (timezone === 'local') {
 | 
			
		||||
    const offset = new Date().getTimezoneOffset();
 | 
			
		||||
    return dayjs(toStringByPicker(value, picker, 'gmt')).add(offset, 'minutes').toISOString();
 | 
			
		||||
    return dayjs(toStringByPicker(value, picker, 'gmt'))
 | 
			
		||||
      .add(offset, 'minutes')
 | 
			
		||||
      .toISOString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (picker === 'year') {
 | 
			
		||||
@ -136,18 +138,24 @@ const getEnd = (offset: any, unit: any) => {
 | 
			
		||||
 | 
			
		||||
export const getDateRanges = () => {
 | 
			
		||||
  return {
 | 
			
		||||
    now: () => dayjs(),
 | 
			
		||||
    today: () => [getStart(0, 'day'), getEnd(0, 'day')],
 | 
			
		||||
    lastWeek: () => [getStart(-1, 'isoWeek'), getEnd(-1, 'isoWeek')],
 | 
			
		||||
    yesterday: () => [getStart(-1, 'day'), getEnd(-1, 'day')],
 | 
			
		||||
    tomorrow: () => [getStart(1, 'day'), getEnd(1, 'day')],
 | 
			
		||||
    thisWeek: () => [getStart(0, 'isoWeek'), getEnd(0, 'isoWeek')],
 | 
			
		||||
    lastWeek: () => [getStart(-1, 'isoWeek'), getEnd(-1, 'isoWeek')],
 | 
			
		||||
    nextWeek: () => [getStart(1, 'isoWeek'), getEnd(1, 'isoWeek')],
 | 
			
		||||
    lastMonth: () => [getStart(-1, 'month'), getEnd(-1, 'month')],
 | 
			
		||||
    thisIsoWeek: () => [getStart(0, 'isoWeek'), getEnd(0, 'isoWeek')],
 | 
			
		||||
    lastIsoWeek: () => [getStart(-1, 'isoWeek'), getEnd(-1, 'isoWeek')],
 | 
			
		||||
    nextIsoWeek: () => [getStart(1, 'isoWeek'), getEnd(1, 'isoWeek')],
 | 
			
		||||
    thisMonth: () => [getStart(0, 'month'), getEnd(0, 'month')],
 | 
			
		||||
    lastMonth: () => [getStart(-1, 'month'), getEnd(-1, 'month')],
 | 
			
		||||
    nextMonth: () => [getStart(1, 'month'), getEnd(1, 'month')],
 | 
			
		||||
    lastQuarter: () => [getStart(-1, 'quarter'), getEnd(-1, 'quarter')],
 | 
			
		||||
    thisQuarter: () => [getStart(0, 'quarter'), getEnd(0, 'quarter')],
 | 
			
		||||
    lastQuarter: () => [getStart(-1, 'quarter'), getEnd(-1, 'quarter')],
 | 
			
		||||
    nextQuarter: () => [getStart(1, 'quarter'), getEnd(1, 'quarter')],
 | 
			
		||||
    lastYear: () => [getStart(-1, 'year'), getEnd(-1, 'year')],
 | 
			
		||||
    thisYear: () => [getStart(0, 'year'), getEnd(0, 'year')],
 | 
			
		||||
    lastYear: () => [getStart(-1, 'year'), getEnd(-1, 'year')],
 | 
			
		||||
    nextYear: () => [getStart(1, 'year'), getEnd(1, 'year')],
 | 
			
		||||
    last7Days: () => [getStart(-6, 'days'), getEnd(0, 'days')],
 | 
			
		||||
    next7Days: () => [getStart(1, 'day'), getEnd(7, 'days')],
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,30 @@
 | 
			
		||||
import { createForm, onFieldValueChange } from '@formily/core';
 | 
			
		||||
import { FieldContext, FormContext } from '@formily/react';
 | 
			
		||||
import { merge } from '@formily/shared';
 | 
			
		||||
import React, { useContext, useMemo } from 'react';
 | 
			
		||||
import React, { useCallback, useContext, useMemo } from 'react';
 | 
			
		||||
import { CollectionFieldOptions } from '../../../collection-manager';
 | 
			
		||||
import { SchemaComponent } from '../../core';
 | 
			
		||||
import { useComponent } from '../../hooks';
 | 
			
		||||
import { FilterContext } from './context';
 | 
			
		||||
 | 
			
		||||
const isDateComponent = {
 | 
			
		||||
  'DatePicker.RangePicker': true,
 | 
			
		||||
  DatePicker: true,
 | 
			
		||||
};
 | 
			
		||||
export interface DynamicComponentProps {
 | 
			
		||||
  value: any;
 | 
			
		||||
  /**
 | 
			
		||||
   * `Filter` 组件左侧选择的字段
 | 
			
		||||
   */
 | 
			
		||||
  collectionField: CollectionFieldOptions;
 | 
			
		||||
  onChange: (value: any) => void;
 | 
			
		||||
  renderSchemaComponent: () => React.JSX.Element;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DynamicComponent = (props) => {
 | 
			
		||||
interface Props {
 | 
			
		||||
  schema: any;
 | 
			
		||||
  value: any;
 | 
			
		||||
  collectionField: CollectionFieldOptions;
 | 
			
		||||
  onChange: (value: any) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const DynamicComponent = (props: Props) => {
 | 
			
		||||
  const { dynamicComponent, disabled } = useContext(FilterContext);
 | 
			
		||||
  const component = useComponent(dynamicComponent);
 | 
			
		||||
  const form = useMemo(() => {
 | 
			
		||||
@ -26,8 +39,8 @@ export const DynamicComponent = (props) => {
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
  }, [JSON.stringify(props.schema), JSON.stringify(props.value)]);
 | 
			
		||||
  const renderSchemaComponent = () => {
 | 
			
		||||
  }, [JSON.stringify(props.value)]);
 | 
			
		||||
  const renderSchemaComponent = useCallback(() => {
 | 
			
		||||
    return (
 | 
			
		||||
      <FieldContext.Provider value={null}>
 | 
			
		||||
        <SchemaComponent
 | 
			
		||||
@ -47,13 +60,14 @@ export const DynamicComponent = (props) => {
 | 
			
		||||
        />
 | 
			
		||||
      </FieldContext.Provider>
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
  }, [props.schema]);
 | 
			
		||||
  return (
 | 
			
		||||
    <FormContext.Provider value={form}>
 | 
			
		||||
      <div data-testid="dynamic-component-filter-item">
 | 
			
		||||
        {component
 | 
			
		||||
          ? React.createElement(component, {
 | 
			
		||||
          ? React.createElement<DynamicComponentProps>(component, {
 | 
			
		||||
              value: props.value,
 | 
			
		||||
              collectionField: props.collectionField,
 | 
			
		||||
              onChange: props?.onChange,
 | 
			
		||||
              renderSchemaComponent,
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@ -3,12 +3,11 @@ import { observer, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import React, { useEffect } from 'react';
 | 
			
		||||
import { useRequest } from '../../../api-client';
 | 
			
		||||
import { useProps } from '../../hooks/useProps';
 | 
			
		||||
import { DatePickerProvider } from '../date-picker';
 | 
			
		||||
import { FilterContext } from './context';
 | 
			
		||||
import { FilterActionDesigner } from './Filter.Action.Designer';
 | 
			
		||||
import { FilterAction } from './FilterAction';
 | 
			
		||||
import { FilterGroup } from './FilterGroup';
 | 
			
		||||
import { SaveDefaultValue } from './SaveDefaultValue';
 | 
			
		||||
import { FilterContext } from './context';
 | 
			
		||||
 | 
			
		||||
const useDef = (options) => {
 | 
			
		||||
  const field = useField<ObjectFieldModel>();
 | 
			
		||||
@ -26,12 +25,12 @@ export const Filter: any = observer(
 | 
			
		||||
        field.dataSource = data?.data || [];
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      field.initialValue = fieldSchema.defaultValue;
 | 
			
		||||
    }, []);
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={className}>
 | 
			
		||||
        <DatePickerProvider value={{ utc: false }}>
 | 
			
		||||
        <FilterContext.Provider
 | 
			
		||||
          value={{
 | 
			
		||||
            field,
 | 
			
		||||
@ -43,9 +42,7 @@ export const Filter: any = observer(
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <FilterGroup {...props} bordered={false} />
 | 
			
		||||
            {/* <pre>{JSON.stringify(field.value, null, 2)}</pre> */}
 | 
			
		||||
        </FilterContext.Provider>
 | 
			
		||||
        </DatePickerProvider>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { CloseCircleOutlined } from '@ant-design/icons';
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import { Cascader, Select, Space } from 'antd';
 | 
			
		||||
import React, { useContext } from 'react';
 | 
			
		||||
import React, { useCallback, useContext, useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCompile } from '../..';
 | 
			
		||||
import { DynamicComponent } from './DynamicComponent';
 | 
			
		||||
@ -14,27 +14,56 @@ export const FilterItem = observer(
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const compile = useCompile();
 | 
			
		||||
    const remove = useContext(RemoveConditionContext);
 | 
			
		||||
    const { schema, fields, operators, dataIndex, operator, setDataIndex, setOperator, value, setValue } = useValues();
 | 
			
		||||
    const {
 | 
			
		||||
      schema,
 | 
			
		||||
      fields,
 | 
			
		||||
      operators,
 | 
			
		||||
      dataIndex,
 | 
			
		||||
      operator,
 | 
			
		||||
      setDataIndex,
 | 
			
		||||
      setOperator,
 | 
			
		||||
      value,
 | 
			
		||||
      setValue,
 | 
			
		||||
      collectionField,
 | 
			
		||||
    } = useValues();
 | 
			
		||||
    const style = useMemo(() => ({ marginBottom: 8 }), []);
 | 
			
		||||
    const fieldNames = useMemo(
 | 
			
		||||
      () => ({
 | 
			
		||||
        label: 'title',
 | 
			
		||||
        value: 'name',
 | 
			
		||||
        children: 'children',
 | 
			
		||||
      }),
 | 
			
		||||
      [],
 | 
			
		||||
    );
 | 
			
		||||
    const onChange = useCallback(
 | 
			
		||||
      (value) => {
 | 
			
		||||
        setDataIndex(value);
 | 
			
		||||
      },
 | 
			
		||||
      [setDataIndex],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const onOperatorsChange = useCallback(
 | 
			
		||||
      (value) => {
 | 
			
		||||
        setOperator(value);
 | 
			
		||||
      },
 | 
			
		||||
      [setOperator],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const removeStyle = useMemo(() => ({ color: '#bfbfbf' }), []);
 | 
			
		||||
    return (
 | 
			
		||||
      // 添加 nc-filter-item 类名是为了帮助编写测试时更容易选中该元素
 | 
			
		||||
      <div style={{ marginBottom: 8 }} className="nc-filter-item">
 | 
			
		||||
      <div style={style} className="nc-filter-item">
 | 
			
		||||
        <Space>
 | 
			
		||||
          <Cascader
 | 
			
		||||
            data-testid="antd-cascader"
 | 
			
		||||
            className={css`
 | 
			
		||||
              width: 160px;
 | 
			
		||||
            `}
 | 
			
		||||
            fieldNames={{
 | 
			
		||||
              label: 'title',
 | 
			
		||||
              value: 'name',
 | 
			
		||||
              children: 'children',
 | 
			
		||||
            }}
 | 
			
		||||
            fieldNames={fieldNames}
 | 
			
		||||
            changeOnSelect={false}
 | 
			
		||||
            value={dataIndex}
 | 
			
		||||
            options={compile(fields)}
 | 
			
		||||
            onChange={(value) => {
 | 
			
		||||
              setDataIndex(value);
 | 
			
		||||
            }}
 | 
			
		||||
            onChange={onChange}
 | 
			
		||||
            placeholder={t('Select field')}
 | 
			
		||||
          />
 | 
			
		||||
          <Select
 | 
			
		||||
@ -45,15 +74,15 @@ export const FilterItem = observer(
 | 
			
		||||
            popupMatchSelectWidth={false}
 | 
			
		||||
            value={operator?.value}
 | 
			
		||||
            options={compile(operators)}
 | 
			
		||||
            onChange={(value) => {
 | 
			
		||||
              setOperator(value);
 | 
			
		||||
            }}
 | 
			
		||||
            onChange={onOperatorsChange}
 | 
			
		||||
            placeholder={t('Comparision')}
 | 
			
		||||
          />
 | 
			
		||||
          {!operator?.noValue ? <DynamicComponent value={value} schema={schema} onChange={setValue} /> : null}
 | 
			
		||||
          {!operator?.noValue ? (
 | 
			
		||||
            <DynamicComponent value={value} schema={schema} collectionField={collectionField} onChange={setValue} />
 | 
			
		||||
          ) : null}
 | 
			
		||||
          {!props.disabled && (
 | 
			
		||||
            <a data-testid="close-icon-button">
 | 
			
		||||
              <CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
 | 
			
		||||
              <CloseCircleOutlined onClick={remove} style={removeStyle} />
 | 
			
		||||
            </a>
 | 
			
		||||
          )}
 | 
			
		||||
        </Space>
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { ArrayField as ArrayFieldModel } from '@formily/core';
 | 
			
		||||
import { ObjectField, observer, useField } from '@formily/react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { RemoveConditionContext } from './context';
 | 
			
		||||
import { FilterGroup } from './FilterGroup';
 | 
			
		||||
import { FilterItem } from './FilterItem';
 | 
			
		||||
import { RemoveConditionContext } from './context';
 | 
			
		||||
 | 
			
		||||
export const FilterItems = observer(
 | 
			
		||||
  (props) => {
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import { Schema } from '@formily/react';
 | 
			
		||||
import { createContext } from 'react';
 | 
			
		||||
 | 
			
		||||
export interface FilterContextProps {
 | 
			
		||||
  field?: ObjectField;
 | 
			
		||||
  field?: ObjectField & { collectionName?: string };
 | 
			
		||||
  fieldSchema?: Schema;
 | 
			
		||||
  dynamicComponent?: any;
 | 
			
		||||
  options?: any[];
 | 
			
		||||
 | 
			
		||||
@ -46,6 +46,7 @@ export const useGetFilterFieldOptions = () => {
 | 
			
		||||
      target: field.target,
 | 
			
		||||
      title: field?.uiSchema?.title || field.name,
 | 
			
		||||
      schema: field?.uiSchema,
 | 
			
		||||
      interface: field.interface,
 | 
			
		||||
      operators:
 | 
			
		||||
        operators?.filter?.((operator) => {
 | 
			
		||||
          return !operator?.visible || operator.visible(field);
 | 
			
		||||
 | 
			
		||||
@ -3,9 +3,22 @@ import { merge } from '@formily/shared';
 | 
			
		||||
import flat, { unflatten } from 'flat';
 | 
			
		||||
import cloneDeep from 'lodash/cloneDeep';
 | 
			
		||||
import get from 'lodash/get';
 | 
			
		||||
import { useContext, useEffect } from 'react';
 | 
			
		||||
import { FilterContext } from './context';
 | 
			
		||||
import { useCallback, useContext, useEffect, useMemo } from 'react';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { FilterContext } from './context';
 | 
			
		||||
 | 
			
		||||
interface UseValuesReturn {
 | 
			
		||||
  fields: any[];
 | 
			
		||||
  collectionField: any;
 | 
			
		||||
  dataIndex: string[];
 | 
			
		||||
  operators: any[];
 | 
			
		||||
  operator: any;
 | 
			
		||||
  schema: any;
 | 
			
		||||
  value: any;
 | 
			
		||||
  setDataIndex: (dataIndex: string[]) => void;
 | 
			
		||||
  setOperator: (operatorValue: string) => void;
 | 
			
		||||
  setValue: (value: any) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// import { useValues } from './useValues';
 | 
			
		||||
const findOption = (dataIndex = [], options) => {
 | 
			
		||||
@ -21,21 +34,27 @@ const findOption = (dataIndex = [], options) => {
 | 
			
		||||
  return option;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useValues = () => {
 | 
			
		||||
  const field = useField<any>();
 | 
			
		||||
export const useValues = (): UseValuesReturn => {
 | 
			
		||||
  const { name } = useCollection();
 | 
			
		||||
  const { getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
  const ctx: any = useContext(FilterContext) || {};
 | 
			
		||||
  const { options } = ctx;
 | 
			
		||||
  const data2value = () => {
 | 
			
		||||
  const field = useField<any>();
 | 
			
		||||
  const { options, collectionName, field: ctxField } = useContext(FilterContext) || {};
 | 
			
		||||
  const values: object = flat(field.value || {});
 | 
			
		||||
  const path = Object.keys(values).shift() || '';
 | 
			
		||||
 | 
			
		||||
  const collectionField = useMemo(() => {
 | 
			
		||||
    const [fieldPath = ''] = path.split('.$');
 | 
			
		||||
    return getCollectionJoinField(`${collectionName || name}.${fieldPath}`);
 | 
			
		||||
  }, [name, path]);
 | 
			
		||||
 | 
			
		||||
  const data2value = useCallback(() => {
 | 
			
		||||
    field.value = flat.unflatten({
 | 
			
		||||
      [`${field.data.dataIndex?.join('.')}.${field.data?.operator?.value}`]: field.data?.value,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
  }, [field]);
 | 
			
		||||
 | 
			
		||||
  const value2data = () => {
 | 
			
		||||
    field.data = field.data || {};
 | 
			
		||||
    const values: object = flat(field.value || {});
 | 
			
		||||
    const path = Object.keys(values).shift() || '';
 | 
			
		||||
    if (!path || !options) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
@ -50,20 +69,20 @@ export const useValues = () => {
 | 
			
		||||
      const fieldNames = dataIndex.concat();
 | 
			
		||||
      fieldNames.pop();
 | 
			
		||||
      const targetField = getCollectionJoinField(`${name}.${fieldNames.join('.')}`);
 | 
			
		||||
      ctx.field.collectionName = targetField?.target;
 | 
			
		||||
      ctxField.collectionName = targetField?.target;
 | 
			
		||||
    } else {
 | 
			
		||||
      ctx.field.collectionName = null;
 | 
			
		||||
      ctxField.collectionName = null;
 | 
			
		||||
    }
 | 
			
		||||
    field.data.operators = operators;
 | 
			
		||||
    field.data.operator = operator;
 | 
			
		||||
    field.data.schema = merge(option?.schema, operator?.schema);
 | 
			
		||||
    field.data.value = get(unflatten(field.value), `${fieldPath}.$${operatorValue}`);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(value2data, [field.path]);
 | 
			
		||||
  return {
 | 
			
		||||
    fields: options,
 | 
			
		||||
    ...(field?.data || {}),
 | 
			
		||||
    setDataIndex(dataIndex) {
 | 
			
		||||
 | 
			
		||||
  const setDataIndex = useCallback(
 | 
			
		||||
    (dataIndex) => {
 | 
			
		||||
      const option = findOption(dataIndex, options);
 | 
			
		||||
      const operator = option?.operators?.[0];
 | 
			
		||||
      field.data = field.data || {};
 | 
			
		||||
@ -77,14 +96,18 @@ export const useValues = () => {
 | 
			
		||||
        const fieldNames = dataIndex.concat();
 | 
			
		||||
        fieldNames.pop();
 | 
			
		||||
        const targetField = getCollectionJoinField(`${name}.${fieldNames.join('.')}`);
 | 
			
		||||
        ctx.field.collectionName = targetField?.target;
 | 
			
		||||
        ctxField.collectionName = targetField?.target;
 | 
			
		||||
      } else {
 | 
			
		||||
        ctx.field.collectionName = null;
 | 
			
		||||
        ctxField.collectionName = null;
 | 
			
		||||
      }
 | 
			
		||||
      field.data.value = operator?.noValue ? operator.default || true : undefined;
 | 
			
		||||
      data2value();
 | 
			
		||||
    },
 | 
			
		||||
    setOperator(operatorValue) {
 | 
			
		||||
    [data2value, field, options],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const setOperator = useCallback(
 | 
			
		||||
    (operatorValue) => {
 | 
			
		||||
      const operator = field.data?.operators?.find?.((item) => item.value === operatorValue);
 | 
			
		||||
      field.data.operator = operator;
 | 
			
		||||
      const option = findOption(field.data.dataIndex, options);
 | 
			
		||||
@ -94,9 +117,23 @@ export const useValues = () => {
 | 
			
		||||
      field.data.value = operator.noValue ? operator.default || true : undefined;
 | 
			
		||||
      data2value();
 | 
			
		||||
    },
 | 
			
		||||
    setValue(value) {
 | 
			
		||||
    [data2value, field.data, options],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const setValue = useCallback(
 | 
			
		||||
    (value) => {
 | 
			
		||||
      field.data.value = value;
 | 
			
		||||
      data2value();
 | 
			
		||||
    },
 | 
			
		||||
    [data2value, field.data],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    fields: options,
 | 
			
		||||
    ...(field?.data || {}),
 | 
			
		||||
    collectionField,
 | 
			
		||||
    setDataIndex,
 | 
			
		||||
    setOperator,
 | 
			
		||||
    setValue,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3,130 +3,70 @@ import { ArrayCollapse, FormLayout, FormItem as Item } from '@formily/antd-v5';
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { Select } from 'antd';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import React, { useEffect } from 'react';
 | 
			
		||||
import React, { useEffect, useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { ACLCollectionFieldProvider } from '../../../acl/ACLProvider';
 | 
			
		||||
import { useBlockRequestContext } from '../../../block-provider/BlockProvider';
 | 
			
		||||
import { useFormActiveFields } from '../../../block-provider';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
 | 
			
		||||
import {
 | 
			
		||||
  Collection,
 | 
			
		||||
  CollectionFieldOptions,
 | 
			
		||||
  useCollection,
 | 
			
		||||
  useCollectionFilterOptions,
 | 
			
		||||
  useCollectionManager,
 | 
			
		||||
} from '../../../collection-manager';
 | 
			
		||||
import { Collection, useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { useRecord } from '../../../record-provider';
 | 
			
		||||
import { GeneralSchemaItems } from '../../../schema-items/GeneralSchemaItems';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings';
 | 
			
		||||
import { VariableInput, getShouldChange } from '../../../schema-settings/VariableInput/VariableInput';
 | 
			
		||||
import useIsAllowToSetDefaultValue from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
 | 
			
		||||
import { useIsShowMultipleSwitch } from '../../../schema-settings/hooks/useIsShowMultipleSwitch';
 | 
			
		||||
import { isVariable, parseVariables, useVariablesCtx } from '../../common/utils/uitls';
 | 
			
		||||
import { useLocalVariables, useVariables } from '../../../variables';
 | 
			
		||||
import useContextVariable from '../../../variables/hooks/useContextVariable';
 | 
			
		||||
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
 | 
			
		||||
import { isSubMode } from '../association-field/util';
 | 
			
		||||
import { BlockItem } from '../block-item';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { DynamicComponentProps } from '../filter/DynamicComponent';
 | 
			
		||||
import { HTMLEncode } from '../input/shared';
 | 
			
		||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
 | 
			
		||||
import { useColorFields } from '../table-v2/Table.Column.Designer';
 | 
			
		||||
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
 | 
			
		||||
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
 | 
			
		||||
import useLazyLoadAssociationFieldOfForm from './hooks/useLazyLoadAssociationFieldOfForm';
 | 
			
		||||
import useLazyLoadAssociationFieldOfSubForm from './hooks/useLazyLoadAssociationFieldOfSubForm';
 | 
			
		||||
import useParseDefaultValue from './hooks/useParseDefaultValue';
 | 
			
		||||
 | 
			
		||||
export const findColumnFieldSchema = (fieldSchema, getCollectionJoinField) => {
 | 
			
		||||
  const childsSchema = new Set();
 | 
			
		||||
  const getAssociationAppends = (schema) => {
 | 
			
		||||
    schema.reduceProperties((_, s) => {
 | 
			
		||||
      const collectionfield = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field']);
 | 
			
		||||
      const isAssociationField = collectionfield && ['belongsTo'].includes(collectionfield.type);
 | 
			
		||||
      if (collectionfield && isAssociationField && s.default?.includes?.('$context')) {
 | 
			
		||||
        childsSchema.add(JSON.stringify({ name: s.name, default: s.default }));
 | 
			
		||||
      } else {
 | 
			
		||||
        getAssociationAppends(s);
 | 
			
		||||
      }
 | 
			
		||||
    }, []);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  getAssociationAppends(fieldSchema);
 | 
			
		||||
  return [...childsSchema];
 | 
			
		||||
};
 | 
			
		||||
function transformData(inputData) {
 | 
			
		||||
  const transformedData = [];
 | 
			
		||||
  const keys = Object.keys(inputData) || [];
 | 
			
		||||
  const values: any[] = Object.values(inputData) || [];
 | 
			
		||||
  for (let i = 0; i < values[0]?.length; i++) {
 | 
			
		||||
    const newObj = {};
 | 
			
		||||
    keys.forEach((key, index) => {
 | 
			
		||||
      newObj[key] = values[index][i];
 | 
			
		||||
    });
 | 
			
		||||
    transformedData.push(newObj);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return transformedData;
 | 
			
		||||
}
 | 
			
		||||
export const FormItem: any = observer(
 | 
			
		||||
  (props: any) => {
 | 
			
		||||
    useEnsureOperatorsValid();
 | 
			
		||||
    const field = useField<Field>();
 | 
			
		||||
    const ctx = useBlockRequestContext();
 | 
			
		||||
    const schema = useFieldSchema();
 | 
			
		||||
    const variablesCtx = useVariablesCtx();
 | 
			
		||||
    const { getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
    const collectionField = getCollectionJoinField(schema['x-collection-field']);
 | 
			
		||||
    const contextVariable = useContextVariable();
 | 
			
		||||
    const variables = useVariables();
 | 
			
		||||
    const { addActiveFieldName } = useFormActiveFields() || {};
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      if (ctx?.block === 'form') {
 | 
			
		||||
        ctx.field.data = ctx.field.data || {};
 | 
			
		||||
        ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
 | 
			
		||||
        ctx.field.data.activeFields.add(schema.name);
 | 
			
		||||
        // 如果默认值是一个变量,则需要解析之后再显示出来
 | 
			
		||||
        if (isVariable(schema?.default) && !schema?.default.includes('$context')) {
 | 
			
		||||
          field.setInitialValue?.(parseVariables(schema.default, variablesCtx));
 | 
			
		||||
        } else if (
 | 
			
		||||
          isVariable(schema?.default) &&
 | 
			
		||||
          schema?.default?.includes('$context') &&
 | 
			
		||||
          collectionField?.interface === 'm2m'
 | 
			
		||||
        ) {
 | 
			
		||||
          // 直接对多
 | 
			
		||||
          const contextData = parseVariables('{{$context}}', variablesCtx);
 | 
			
		||||
          let iniValues = [];
 | 
			
		||||
          contextData?.map((v) => {
 | 
			
		||||
            const data = parseVariables(schema.default, { $context: v });
 | 
			
		||||
            iniValues = iniValues.concat(data).map((v) => {
 | 
			
		||||
              delete v[collectionField.through];
 | 
			
		||||
              return v;
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
          const data = _.uniqBy(iniValues, 'id');
 | 
			
		||||
          field.setInitialValue?.(data.length > 0 ? data : [{}]);
 | 
			
		||||
        } else if (
 | 
			
		||||
          collectionField?.interface === 'o2m' &&
 | 
			
		||||
          ['SubTable', 'Nester'].includes(schema?.['x-component-props']?.['mode']) // 间接对多
 | 
			
		||||
        ) {
 | 
			
		||||
          const childrenFieldWithDefault = findColumnFieldSchema(schema, getCollectionJoinField);
 | 
			
		||||
          // 子表格/子表单中找出所有belongsTo字段的上下文默认值
 | 
			
		||||
          if (childrenFieldWithDefault.length > 0) {
 | 
			
		||||
            const tableData = parseVariables('{{$context}}', variablesCtx);
 | 
			
		||||
            const contextData = {};
 | 
			
		||||
            // 将数据拍平
 | 
			
		||||
            childrenFieldWithDefault?.forEach((s: any) => {
 | 
			
		||||
              const child = JSON.parse(s);
 | 
			
		||||
              tableData?.map((v) => {
 | 
			
		||||
                contextData[child.name] = _.uniqBy(
 | 
			
		||||
                  (contextData[child.name] || []).concat(parseVariables(child.default, { $context: v })),
 | 
			
		||||
                  'id',
 | 
			
		||||
                );
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
            const initValues = transformData(contextData);
 | 
			
		||||
            field.setInitialValue?.(initValues);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }, []);
 | 
			
		||||
      variables?.registerVariable(contextVariable);
 | 
			
		||||
    }, [contextVariable]);
 | 
			
		||||
 | 
			
		||||
    // 需要放在注冊完变量之后
 | 
			
		||||
    useParseDefaultValue();
 | 
			
		||||
    useLazyLoadAssociationFieldOfForm();
 | 
			
		||||
    useLazyLoadAssociationFieldOfSubForm();
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      addActiveFieldName?.(schema.name as string);
 | 
			
		||||
    }, [addActiveFieldName, schema.name]);
 | 
			
		||||
 | 
			
		||||
    const showTitle = schema['x-decorator-props']?.showTitle ?? true;
 | 
			
		||||
    return (
 | 
			
		||||
      <ACLCollectionFieldProvider>
 | 
			
		||||
        <BlockItem className={'nb-form-item'}>
 | 
			
		||||
          <Item
 | 
			
		||||
            className={cx(
 | 
			
		||||
    const extra = useMemo(() => {
 | 
			
		||||
      return typeof field.description === 'string' ? (
 | 
			
		||||
        <div
 | 
			
		||||
          dangerouslySetInnerHTML={{
 | 
			
		||||
            __html: HTMLEncode(field.description).split('\n').join('<br/>'),
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      ) : (
 | 
			
		||||
        field.description
 | 
			
		||||
      );
 | 
			
		||||
    }, [field.description]);
 | 
			
		||||
    const className = useMemo(() => {
 | 
			
		||||
      return cx(
 | 
			
		||||
        css`
 | 
			
		||||
          & .ant-space {
 | 
			
		||||
            flex-wrap: wrap;
 | 
			
		||||
@ -139,20 +79,13 @@ export const FormItem: any = observer(
 | 
			
		||||
            }
 | 
			
		||||
          `]: showTitle === false,
 | 
			
		||||
        },
 | 
			
		||||
            )}
 | 
			
		||||
            {...props}
 | 
			
		||||
            extra={
 | 
			
		||||
              typeof field.description === 'string' ? (
 | 
			
		||||
                <div
 | 
			
		||||
                  dangerouslySetInnerHTML={{
 | 
			
		||||
                    __html: HTMLEncode(field.description).split('\n').join('<br/>'),
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
              ) : (
 | 
			
		||||
                field.description
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          />
 | 
			
		||||
      );
 | 
			
		||||
    }, [showTitle]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <ACLCollectionFieldProvider>
 | 
			
		||||
        <BlockItem className={'nb-form-item'}>
 | 
			
		||||
          <Item className={className} {...props} extra={extra} />
 | 
			
		||||
        </BlockItem>
 | 
			
		||||
      </ACLCollectionFieldProvider>
 | 
			
		||||
    );
 | 
			
		||||
@ -161,12 +94,17 @@ export const FormItem: any = observer(
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
FormItem.Designer = function Designer() {
 | 
			
		||||
  let targetField;
 | 
			
		||||
  const { getCollectionFields, getInterface, getCollectionJoinField, getCollection, isTitleField } =
 | 
			
		||||
    useCollectionManager();
 | 
			
		||||
  const {
 | 
			
		||||
    getCollectionFields,
 | 
			
		||||
    getInterface,
 | 
			
		||||
    getCollectionJoinField,
 | 
			
		||||
    getCollection,
 | 
			
		||||
    isTitleField,
 | 
			
		||||
    getAllCollectionsInheritChain,
 | 
			
		||||
  } = useCollectionManager();
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const ctx = useBlockRequestContext();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
@ -174,11 +112,10 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const IsShowMultipleSwitch = useIsShowMultipleSwitch();
 | 
			
		||||
  const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']);
 | 
			
		||||
  if (collectionField?.target) {
 | 
			
		||||
    targetField = getCollectionJoinField(
 | 
			
		||||
      `${collectionField.target}.${fieldSchema['x-component-props']?.fieldNames?.label || 'id'}`,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables();
 | 
			
		||||
  const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue();
 | 
			
		||||
  const isAddNewForm = useIsAddNewForm();
 | 
			
		||||
 | 
			
		||||
  const targetCollection = getCollection(collectionField?.target);
 | 
			
		||||
  const interfaceConfig = getInterface(collectionField?.interface);
 | 
			
		||||
@ -190,7 +127,7 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
  const fieldModeOptions = useFieldModeOptions();
 | 
			
		||||
  const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o'].includes(collectionField?.interface);
 | 
			
		||||
  const isTableField = fieldSchema['x-component'] === 'TableField';
 | 
			
		||||
  const isFileField = isFileCollection(targetCollection);
 | 
			
		||||
  const isFileField = isFileCollection(targetCollection as any);
 | 
			
		||||
  const initialValue = {
 | 
			
		||||
    title: field.title === originalTitle ? undefined : field.title,
 | 
			
		||||
  };
 | 
			
		||||
@ -214,8 +151,6 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
  if (fieldSchema['x-read-pretty'] === true) {
 | 
			
		||||
    readOnlyMode = 'read-pretty';
 | 
			
		||||
  }
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(collectionField?.target);
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
 | 
			
		||||
  const fieldMode = field?.componentProps?.['mode'] || (isFileField ? 'FileManager' : 'Select');
 | 
			
		||||
  const isSelectFieldMode = isAssociationField && fieldMode === 'Select';
 | 
			
		||||
  const isSubFormMode = fieldSchema['x-component-props']?.mode === 'Nester';
 | 
			
		||||
@ -365,10 +300,6 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
            const schema = {
 | 
			
		||||
              ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
            };
 | 
			
		||||
            // return;
 | 
			
		||||
            // if (['number'].includes(collectionField?.interface) && collectionField?.uiSchema?.['x-component-props']?.['stringMode'] === true) {
 | 
			
		||||
            //   rules['numberStringMode'] = true;
 | 
			
		||||
            // }
 | 
			
		||||
            if (['percent'].includes(collectionField?.interface)) {
 | 
			
		||||
              for (const rule of rules) {
 | 
			
		||||
                if (!!rule.maxValue || !!rule.minValue) {
 | 
			
		||||
@ -391,41 +322,33 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {form &&
 | 
			
		||||
        !form?.readPretty &&
 | 
			
		||||
        isShowDefaultValue(collectionField, getInterface) &&
 | 
			
		||||
        !isPatternDisabled(fieldSchema) && <SchemaSettings.DefaultValue />}
 | 
			
		||||
      {isAllowToSetDefaultValue() && <SchemaSettings.DefaultValue />}
 | 
			
		||||
      {isSelectFieldMode && !field.readPretty && (
 | 
			
		||||
        <SchemaSettings.ModalItem
 | 
			
		||||
          title={t('Set the data scope')}
 | 
			
		||||
          schema={
 | 
			
		||||
            {
 | 
			
		||||
              type: 'object',
 | 
			
		||||
              title: t('Set the data scope'),
 | 
			
		||||
              properties: {
 | 
			
		||||
                filter: {
 | 
			
		||||
                  defaultValue: defaultFilter,
 | 
			
		||||
                  enum: dataSource,
 | 
			
		||||
                  'x-component': 'Filter',
 | 
			
		||||
                  'x-component-props': {
 | 
			
		||||
                    collectionName: collectionField?.target,
 | 
			
		||||
                    dynamicComponent: (props) =>
 | 
			
		||||
                      FilterDynamicComponent({
 | 
			
		||||
                        ...props,
 | 
			
		||||
                        form,
 | 
			
		||||
                        collectionField,
 | 
			
		||||
                        rootCollection: ctx.props.collection || ctx.props.resource,
 | 
			
		||||
                      }),
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            } as ISchema
 | 
			
		||||
          }
 | 
			
		||||
        <SchemaSettings.DataScope
 | 
			
		||||
          collectionName={collectionField?.target}
 | 
			
		||||
          defaultFilter={fieldSchema?.['x-component-props']?.service?.params?.filter || {}}
 | 
			
		||||
          form={form}
 | 
			
		||||
          dynamicComponent={(props: DynamicComponentProps) => {
 | 
			
		||||
            return (
 | 
			
		||||
              <VariableInput
 | 
			
		||||
                {...props}
 | 
			
		||||
                form={form}
 | 
			
		||||
                collectionField={props.collectionField}
 | 
			
		||||
                record={record}
 | 
			
		||||
                shouldChange={getShouldChange({
 | 
			
		||||
                  collectionField: props.collectionField,
 | 
			
		||||
                  variables,
 | 
			
		||||
                  localVariables,
 | 
			
		||||
                  getAllCollectionsInheritChain,
 | 
			
		||||
                })}
 | 
			
		||||
              />
 | 
			
		||||
            );
 | 
			
		||||
          }}
 | 
			
		||||
          onSubmit={({ filter }) => {
 | 
			
		||||
            filter = removeNullCondition(filter);
 | 
			
		||||
            _.set(field.componentProps, 'service.params.filter', filter);
 | 
			
		||||
            fieldSchema['x-component-props'] = field.componentProps;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
            void dn.emit('patch', {
 | 
			
		||||
              schema: {
 | 
			
		||||
                ['x-uid']: fieldSchema['x-uid'],
 | 
			
		||||
                'x-component-props': field.componentProps,
 | 
			
		||||
@ -450,7 +373,17 @@ FormItem.Designer = function Designer() {
 | 
			
		||||
            schema['x-component-props'] = fieldSchema['x-component-props'];
 | 
			
		||||
            field.componentProps = field.componentProps || {};
 | 
			
		||||
            field.componentProps.mode = mode;
 | 
			
		||||
            dn.emit('patch', {
 | 
			
		||||
 | 
			
		||||
            // 子表单状态不允许设置默认值
 | 
			
		||||
            if (isSubMode(fieldSchema) && isAddNewForm) {
 | 
			
		||||
              // @ts-ignore
 | 
			
		||||
              schema.default = null;
 | 
			
		||||
              fieldSchema.default = null;
 | 
			
		||||
              field.setInitialValue(null);
 | 
			
		||||
              field.setValue(null);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void dn.emit('patch', {
 | 
			
		||||
              schema,
 | 
			
		||||
            });
 | 
			
		||||
            dn.refresh();
 | 
			
		||||
@ -800,10 +733,9 @@ export function isFileCollection(collection: Collection) {
 | 
			
		||||
 | 
			
		||||
FormItem.FilterFormDesigner = FilterFormDesigner;
 | 
			
		||||
 | 
			
		||||
export function getFieldDefaultValue(fieldSchema: ISchema, collectionField: CollectionFieldOptions) {
 | 
			
		||||
  const result = fieldSchema?.default ?? collectionField?.defaultValue;
 | 
			
		||||
  if (collectionField?.uiSchema?.['x-component'] === 'DatePicker' && result) {
 | 
			
		||||
    return dayjs(result);
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
function useIsAddNewForm() {
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const isAddNewForm = _.isEmpty(_.omit(record, '__parent'));
 | 
			
		||||
 | 
			
		||||
  return isAddNewForm;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -382,7 +382,7 @@ export const EditComponent = () => {
 | 
			
		||||
  const fieldModeOptions = useFieldModeOptions();
 | 
			
		||||
  const isAssociationField = ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(collectionField?.type);
 | 
			
		||||
  const targetCollection = getCollection(collectionField?.target);
 | 
			
		||||
  const isFileField = isFileCollection(targetCollection);
 | 
			
		||||
  const isFileField = isFileCollection(targetCollection as any);
 | 
			
		||||
 | 
			
		||||
  return isAssociationField && fieldModeOptions ? (
 | 
			
		||||
    <SchemaSettings.SelectItem
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,33 @@
 | 
			
		||||
import { transformValue } from '../../hooks/useSpecialCase';
 | 
			
		||||
 | 
			
		||||
describe('transformValue', () => {
 | 
			
		||||
  it('value is an array', () => {
 | 
			
		||||
    const value = [{ a: 3 }, { a: 4 }];
 | 
			
		||||
    const deps = {
 | 
			
		||||
      field: {
 | 
			
		||||
        value: [{ a: 1, b: 11 }],
 | 
			
		||||
      },
 | 
			
		||||
      subFieldSchema: {
 | 
			
		||||
        name: 'a',
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    expect(transformValue(value, deps as any)).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        {
 | 
			
		||||
          "a": {
 | 
			
		||||
            "a": 3,
 | 
			
		||||
          },
 | 
			
		||||
          "b": 11,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "__notFromDatabase": true,
 | 
			
		||||
          "a": {
 | 
			
		||||
            "a": 4,
 | 
			
		||||
          },
 | 
			
		||||
          "b": 11,
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
    `);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,50 @@
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { useField, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import { useEffect } from 'react';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../../collection-manager';
 | 
			
		||||
import { useVariables } from '../../../../variables';
 | 
			
		||||
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用于懒加载 Form 区块中只用于展示的关联字段的值
 | 
			
		||||
 */
 | 
			
		||||
const useLazyLoadAssociationFieldOfForm = () => {
 | 
			
		||||
  const { name } = useCollection();
 | 
			
		||||
  const { getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
 | 
			
		||||
  const schemaName = fieldSchema.name.toString();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // 在数据表管理页面,也存在 `a.b` 之类的 schema name,其 collectionName 为 fields,所以在这里排除一下 `name === 'fields'` 的情况
 | 
			
		||||
    if (!schemaName.includes('.') || !variables || name === 'fields') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const formVariable = {
 | 
			
		||||
      name: '$nForm',
 | 
			
		||||
      ctx: form.values,
 | 
			
		||||
      collectionName: name,
 | 
			
		||||
    };
 | 
			
		||||
    const variableString = `{{ $nForm.${schemaName} }}`;
 | 
			
		||||
    const collectionField = getCollectionJoinField(`${name}.${schemaName}`);
 | 
			
		||||
 | 
			
		||||
    if (!collectionField) {
 | 
			
		||||
      return console.error(new Error(`useLazyLoadAssociationField: ${schemaName} not found in collection ${name}`));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    variables
 | 
			
		||||
      .parseVariable(variableString, formVariable)
 | 
			
		||||
      .then((value) => {
 | 
			
		||||
        field.value = transformVariableValue(value, { targetCollectionField: collectionField });
 | 
			
		||||
      })
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        console.error(err);
 | 
			
		||||
      });
 | 
			
		||||
  }, [schemaName.includes('.') ? form.values[schemaName.split('.')[0]] : null]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useLazyLoadAssociationFieldOfForm;
 | 
			
		||||
@ -0,0 +1,62 @@
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import { useEffect } from 'react';
 | 
			
		||||
import { useRecord } from '../../../..';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../../collection-manager';
 | 
			
		||||
import { useFlag } from '../../../../flag-provider';
 | 
			
		||||
import { useVariables } from '../../../../variables';
 | 
			
		||||
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用于懒加载 subForm 区块中的关系字段
 | 
			
		||||
 */
 | 
			
		||||
const useLazyLoadAssociationFieldOfSubForm = () => {
 | 
			
		||||
  const { name } = useCollection();
 | 
			
		||||
  const { getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
  const { isInAssignFieldValues } = useFlag() || {};
 | 
			
		||||
 | 
			
		||||
  const schemaName = fieldSchema.name.toString();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (isInAssignFieldValues) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const collectionField = getCollectionJoinField(`${name}.${schemaName}`);
 | 
			
		||||
 | 
			
		||||
    if (!collectionField) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const cloneRecord = { ...record };
 | 
			
		||||
    delete cloneRecord['__parent'];
 | 
			
		||||
 | 
			
		||||
    if (_.isEmpty(cloneRecord) || !variables || record[schemaName] != null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 通过模拟一个 record 变量,用解析变量的方法去获取关系字段的值
 | 
			
		||||
    const recordVariable = {
 | 
			
		||||
      name: '$nRecord',
 | 
			
		||||
      ctx: cloneRecord,
 | 
			
		||||
      collectionName: name,
 | 
			
		||||
    };
 | 
			
		||||
    const variableString = `{{ $nRecord.${schemaName} }}`;
 | 
			
		||||
 | 
			
		||||
    variables
 | 
			
		||||
      .parseVariable(variableString, recordVariable)
 | 
			
		||||
      .then((value) => {
 | 
			
		||||
        field.value = transformVariableValue(value, { targetCollectionField: collectionField });
 | 
			
		||||
      })
 | 
			
		||||
      .catch((err) => {
 | 
			
		||||
        console.error(err);
 | 
			
		||||
      });
 | 
			
		||||
  }, [field, name, record, schemaName, isInAssignFieldValues]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useLazyLoadAssociationFieldOfSubForm;
 | 
			
		||||
@ -0,0 +1,112 @@
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { reaction } from '@formily/reactive';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import { useEffect, useRef } from 'react';
 | 
			
		||||
import { useRecord, useRecordIndex } from '../../../../../src/record-provider';
 | 
			
		||||
import { useCollection } from '../../../../collection-manager';
 | 
			
		||||
import { useFlag } from '../../../../flag-provider';
 | 
			
		||||
import { DEBOUNCE_WAIT, useLocalVariables, useVariables } from '../../../../variables';
 | 
			
		||||
import { getPath } from '../../../../variables/utils/getPath';
 | 
			
		||||
import { getVariableName } from '../../../../variables/utils/getVariableName';
 | 
			
		||||
import { isVariable } from '../../../../variables/utils/isVariable';
 | 
			
		||||
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
 | 
			
		||||
import { isSubMode } from '../../association-field/util';
 | 
			
		||||
import { isFromDatabase, useSpecialCase } from './useSpecialCase';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用于解析并设置 FormItem 的默认值
 | 
			
		||||
 */
 | 
			
		||||
const useParseDefaultValue = () => {
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const { isInAssignFieldValues, isInSetDefaultValueDialog, isInFormDataTemplate } = useFlag() || {};
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
  const { isSpecialCase, setDefaultValue } = useSpecialCase();
 | 
			
		||||
  const index = useRecordIndex();
 | 
			
		||||
 | 
			
		||||
  // 需要保存 record 的初始值,因为设置默认值的时候 record 会被修改,导致初始值丢失
 | 
			
		||||
  const recordRef = useRef(_.omit(record, '__parent'));
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (
 | 
			
		||||
      fieldSchema.default == null ||
 | 
			
		||||
      isInSetDefaultValueDialog ||
 | 
			
		||||
      isInFormDataTemplate ||
 | 
			
		||||
      isSubMode(fieldSchema) ||
 | 
			
		||||
      // 根据 record 是否为空,判断当前是否是新建状态,编辑状态下不需要设置默认值,否则会覆盖用户输入的值,只有新建状态下才需要设置默认值
 | 
			
		||||
      (!_.isEmpty(recordRef.current) && isFromDatabase(record) && !isInAssignFieldValues)
 | 
			
		||||
    ) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const _run = async () => {
 | 
			
		||||
      // 如果默认值是一个变量,则需要解析之后再显示出来
 | 
			
		||||
      if (isVariable(fieldSchema.default) && variables && field) {
 | 
			
		||||
        // 一个变量字符串如果显示出来会比较奇怪
 | 
			
		||||
        if (isVariable(field.value)) {
 | 
			
		||||
          field.setValue(null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        field.loading = true;
 | 
			
		||||
        const collectionField = !fieldSchema.name.toString().includes('.') && getField(fieldSchema.name);
 | 
			
		||||
 | 
			
		||||
        if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
          if (!collectionField) {
 | 
			
		||||
            throw new Error(`useParseDefaultValue: can not find field ${fieldSchema.name}`);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const value = transformVariableValue(await variables.parseVariable(fieldSchema.default, localVariables), {
 | 
			
		||||
          targetCollectionField: collectionField,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (value == null || value === '') {
 | 
			
		||||
          field.setValue(null);
 | 
			
		||||
        } else if (isSpecialCase()) {
 | 
			
		||||
          // 只需要设置一次就可以了
 | 
			
		||||
          if (index === 0) {
 | 
			
		||||
            setDefaultValue(value);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          // TODO: 可能会因为异步时序问题导致设置默认值无效
 | 
			
		||||
          // eslint-disable-next-line promise/catch-or-return
 | 
			
		||||
          void Promise.resolve().then(() => {
 | 
			
		||||
            field.setInitialValue(value);
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        field.loading = false;
 | 
			
		||||
 | 
			
		||||
        // 如果不是一个有效的变量字符串(如:`{{ $user.name }}`)却依然包含 `{{` 和 `}}`,
 | 
			
		||||
        // 则可以断定是一个需要 compile(`useCompile` 返回的函数) 的字符串,这样的字符串会由 formily 自动解析,无需在这里赋值
 | 
			
		||||
      } else if (!/\{\{.+\}\}/g.test(fieldSchema.default) && field.setInitialValue) {
 | 
			
		||||
        field.setInitialValue(fieldSchema.default);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // 使用防抖,提高性能和用户体验
 | 
			
		||||
    const run = _.debounce(_run, DEBOUNCE_WAIT);
 | 
			
		||||
 | 
			
		||||
    void _run();
 | 
			
		||||
 | 
			
		||||
    if (isVariable(fieldSchema.default)) {
 | 
			
		||||
      const variableName = getVariableName(fieldSchema.default);
 | 
			
		||||
      const variableValue = localVariables.find((item) => item.name === variableName);
 | 
			
		||||
 | 
			
		||||
      if (variableValue) {
 | 
			
		||||
        // 实现联动的效果,当依赖的变量变化时(如 `$nForm` 变量),重新解析默认值
 | 
			
		||||
        const dispose = reaction(() => {
 | 
			
		||||
          return _.get({ [variableName]: variableValue?.ctx }, getPath(fieldSchema.default));
 | 
			
		||||
        }, run);
 | 
			
		||||
 | 
			
		||||
        return dispose;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [fieldSchema.default]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useParseDefaultValue;
 | 
			
		||||
@ -0,0 +1,174 @@
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { Schema, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import { useCallback, useEffect, useMemo } from 'react';
 | 
			
		||||
import { CollectionFieldOptions, useCollection, useCollectionManager } from '../../../../collection-manager';
 | 
			
		||||
import { isSubMode } from '../../association-field/util';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * #### 处理 `子表单` 和 `子表格` 中的特殊情况
 | 
			
		||||
 *
 | 
			
		||||
 * - 提供一个判断当前是否是特殊情况的方法
 | 
			
		||||
 * - 提供一个设置默认值的方法
 | 
			
		||||
 */
 | 
			
		||||
export const useSpecialCase = () => {
 | 
			
		||||
  const form = useForm();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
  const { getCollectionField } = useCollectionManager();
 | 
			
		||||
 | 
			
		||||
  const collectionField = useMemo(() => {
 | 
			
		||||
    return getField(fieldSchema.name);
 | 
			
		||||
  }, [fieldSchema.name, getField]);
 | 
			
		||||
 | 
			
		||||
  const isSpecialCase = useCallback(() => {
 | 
			
		||||
    return isSpecialCaseField({ collectionField, fieldSchema, getCollectionField });
 | 
			
		||||
  }, [collectionField, fieldSchema, getCollectionField]);
 | 
			
		||||
 | 
			
		||||
  const setDefaultValue = useCallback(
 | 
			
		||||
    (value: any) => {
 | 
			
		||||
      const parentFieldSchema = getParentFieldSchema(fieldSchema);
 | 
			
		||||
      if (parentFieldSchema) {
 | 
			
		||||
        const parentField: any = form.query(parentFieldSchema.name).take();
 | 
			
		||||
        if (parentField) {
 | 
			
		||||
          parentField.setInitialValue(transformValue(value, { field: parentField, subFieldSchema: fieldSchema }));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    [fieldSchema, form],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    /**
 | 
			
		||||
     * 特殊情况指的是:当前字段是 `对一` 字段且存在于 `对多` 字段的 `子表格` 或 `子表单` 中
 | 
			
		||||
     * 详细说明见:https://nocobase.feishu.cn/docx/EmNEdEBOnoQohUx2UmBcqIQ5nyh#CUdLdy6OpoPKjyx9DLPc3lqknVc
 | 
			
		||||
     */
 | 
			
		||||
    isSpecialCase,
 | 
			
		||||
    setDefaultValue,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通过存在于 `子表格` 和 `子表单` 中的字段,获取父级字段的 fieldSchema
 | 
			
		||||
 * @param fieldSchema
 | 
			
		||||
 */
 | 
			
		||||
export function getParentFieldSchema(fieldSchema: Schema) {
 | 
			
		||||
  while (fieldSchema?.parent) {
 | 
			
		||||
    fieldSchema = fieldSchema.parent;
 | 
			
		||||
 | 
			
		||||
    if (isSubMode(fieldSchema)) {
 | 
			
		||||
      return fieldSchema;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return fieldSchema;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 特殊情况指的是:当前字段是 `对一` 字段且存在于 `对多` 字段的 `子表格` 或 `子表单` 中
 | 
			
		||||
 * 详细说明见:https://nocobase.feishu.cn/docx/EmNEdEBOnoQohUx2UmBcqIQ5nyh#CUdLdy6OpoPKjyx9DLPc3lqknVc
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function isSpecialCaseField({
 | 
			
		||||
  collectionField,
 | 
			
		||||
  fieldSchema,
 | 
			
		||||
  getCollectionField,
 | 
			
		||||
}: {
 | 
			
		||||
  collectionField: CollectionFieldOptions;
 | 
			
		||||
  fieldSchema: Schema;
 | 
			
		||||
  getCollectionField: (name: string) => CollectionFieldOptions;
 | 
			
		||||
}) {
 | 
			
		||||
  if (collectionField && ['hasOne', 'belongsTo'].includes(collectionField.type) && fieldSchema) {
 | 
			
		||||
    const parentFieldSchema = getParentFieldSchema(fieldSchema);
 | 
			
		||||
    if (parentFieldSchema && parentFieldSchema['x-collection-field']) {
 | 
			
		||||
      const parentCollectionField = getCollectionField(parentFieldSchema['x-collection-field']);
 | 
			
		||||
      if (['hasMany', 'belongsToMany'].includes(parentCollectionField?.type)) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 将 `对多` 字段的子表单或子表格中的 `对一` 字段的值转换成 `对多` 字段的值
 | 
			
		||||
 * @param value `子表单` 和 `子表格` 中的 `对一` 字段的经过解析之后的变量的值
 | 
			
		||||
 * @param deps
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function transformValue(
 | 
			
		||||
  value: any,
 | 
			
		||||
  deps: {
 | 
			
		||||
    field: Field;
 | 
			
		||||
    subFieldSchema: Schema;
 | 
			
		||||
  },
 | 
			
		||||
) {
 | 
			
		||||
  const { field, subFieldSchema } = deps;
 | 
			
		||||
  const oldValues = field.value;
 | 
			
		||||
  let newValues = null;
 | 
			
		||||
 | 
			
		||||
  if (Array.isArray(value)) {
 | 
			
		||||
    newValues = value.map((item) => {
 | 
			
		||||
      return {
 | 
			
		||||
        [subFieldSchema.name]: item,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  } else {
 | 
			
		||||
    newValues = [
 | 
			
		||||
      {
 | 
			
		||||
        [subFieldSchema.name]: value,
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const result = _.assignWith([...oldValues], newValues, (oldValue, newValue) => {
 | 
			
		||||
    if (!oldValue && newValue) {
 | 
			
		||||
      // 用于标记这个值不是从数据库中获取的
 | 
			
		||||
      newValue.__notFromDatabase = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 之所以取第一个值,是因为 `对一` 字段的默认值可能是异步的,导致会赋默认值值会晚于普通字段,导致普通字段的值会被覆盖
 | 
			
		||||
    return _.assign({ ...oldValues[0] }, newValue);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断一个 record 是否是从数据库中获取的,如果是则返回 true,否则返回 false
 | 
			
		||||
 * @param value useRecord 返回的值
 | 
			
		||||
 * @returns boolean
 | 
			
		||||
 */
 | 
			
		||||
export function isFromDatabase(value: Record<string, any>) {
 | 
			
		||||
  if (!value) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return !value.__notFromDatabase;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 解决 `子表格` 中的 “一对多/多对一” 字段的默认值问题。
 | 
			
		||||
 *
 | 
			
		||||
 * 问题:如果子表格是空的,会导致默认值不会被设置,这是因为子表格中没有 FormItem 渲染,而设置默认值的逻辑就在 FormItem 中。
 | 
			
		||||
 *
 | 
			
		||||
 * 解决方法:
 | 
			
		||||
 * 1. 如果子表格是空的,就设置一个值,这样会渲染出 FormItem。
 | 
			
		||||
 * 2. 在 FormItem 中会设置默认值。
 | 
			
		||||
 * 3. 如果子表格中没有设置默认值,就会再把子表格重置为空。
 | 
			
		||||
 * @param param0
 | 
			
		||||
 */
 | 
			
		||||
export const useSubTableSpecialCase = ({ field }) => {
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (_.isEmpty(field.value)) {
 | 
			
		||||
      field.value = [{}];
 | 
			
		||||
      // 因为默认值的解析是异步的,所以下面的代码会优先于默认值的设置,这样就防止了设置完默认值后又被清空的问题
 | 
			
		||||
      Promise.resolve()
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          field.value = [];
 | 
			
		||||
        })
 | 
			
		||||
        .catch(console.error);
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
};
 | 
			
		||||
@ -5,12 +5,11 @@ import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useDetailsBlockContext } from '../../../block-provider/DetailsBlockProvider';
 | 
			
		||||
import { useCollection } from '../../../collection-manager';
 | 
			
		||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { useSortFields } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
import { useDesignable } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
 | 
			
		||||
 | 
			
		||||
export const FormDesigner = () => {
 | 
			
		||||
  const { name, title } = useCollection();
 | 
			
		||||
@ -74,12 +73,11 @@ export const DetailsDesigner = () => {
 | 
			
		||||
  const template = useSchemaTemplate();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { service } = useDetailsBlockContext();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const sortFields = useSortFields(name);
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
 | 
			
		||||
  const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
 | 
			
		||||
  const sort = defaultSort?.map((item: string) => {
 | 
			
		||||
@ -96,25 +94,10 @@ export const DetailsDesigner = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <GeneralSchemaDesigner template={template} title={title || name}>
 | 
			
		||||
      <SchemaSettings.BlockTitleItem />
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                // title: '数据范围',
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {
 | 
			
		||||
                  dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={name}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          filter = removeNullCondition(filter);
 | 
			
		||||
          const params = field.decoratorProps.params || {};
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,14 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { FormLayout } from '@formily/antd-v5';
 | 
			
		||||
import { createForm, Field, onFieldChange, onFieldInit, onFieldReact, onFormInputChange } from '@formily/core';
 | 
			
		||||
import { createForm, Field, Form as FormilyForm, onFieldInit, onFormInputChange } from '@formily/core';
 | 
			
		||||
import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { autorun } from '@formily/reactive';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { ConfigProvider, Spin } from 'antd';
 | 
			
		||||
import React, { useEffect, useMemo } from 'react';
 | 
			
		||||
import { useActionContext } from '..';
 | 
			
		||||
import { useAttach, useComponent } from '../..';
 | 
			
		||||
import { useLocalVariables, useVariables } from '../../../variables';
 | 
			
		||||
import { useProps } from '../../hooks/useProps';
 | 
			
		||||
import { linkageMergeAction } from './utils';
 | 
			
		||||
 | 
			
		||||
@ -66,81 +68,91 @@ const getLinkageRules = (fieldSchema) => {
 | 
			
		||||
  return linkageRules;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const WithForm = (props) => {
 | 
			
		||||
interface WithFormProps {
 | 
			
		||||
  form: FormilyForm;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const WithForm = (props: WithFormProps) => {
 | 
			
		||||
  const { form } = props;
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { setFormValueChanged } = useActionContext();
 | 
			
		||||
  const linkageRules =
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables({ currentForm: form });
 | 
			
		||||
  const linkageRules: any[] =
 | 
			
		||||
    (getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const id = uid();
 | 
			
		||||
 | 
			
		||||
    form.addEffects(id, () => {
 | 
			
		||||
      onFormInputChange((form) => {
 | 
			
		||||
      onFormInputChange(() => {
 | 
			
		||||
        setFormValueChanged?.(true);
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (props.disabled) {
 | 
			
		||||
      form.disabled = props.disabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      form.removeEffects(id);
 | 
			
		||||
    };
 | 
			
		||||
  }, []);
 | 
			
		||||
  }, [props.disabled]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const id = uid();
 | 
			
		||||
    const disposes = [];
 | 
			
		||||
 | 
			
		||||
    form.addEffects(id, () => {
 | 
			
		||||
      return linkageRules.map((v) => {
 | 
			
		||||
        return v.actions?.map((h) => {
 | 
			
		||||
          if (h.targetFields) {
 | 
			
		||||
      linkageRules.forEach((v) => {
 | 
			
		||||
        v.actions?.forEach((h) => {
 | 
			
		||||
          if (h.targetFields?.length) {
 | 
			
		||||
            const fields = h.targetFields.join(',');
 | 
			
		||||
            onFieldInit(`*(${fields})`, (field: any, form) => {
 | 
			
		||||
              field['initProperty'] = {
 | 
			
		||||
                display: field.display,
 | 
			
		||||
                required: field.required,
 | 
			
		||||
                pattern: field.pattern,
 | 
			
		||||
                value: field.value || field.initialValue,
 | 
			
		||||
              };
 | 
			
		||||
            });
 | 
			
		||||
            onFieldChange(`*(${fields})`, ['value', 'required', 'pattern', 'display'], (field: any) => {
 | 
			
		||||
              field.linkageProperty = {
 | 
			
		||||
                display: field.linkageProperty?.display,
 | 
			
		||||
              };
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // 当 `linkageRules` 变更时,需要把 `field` 上之前已设置的值还原成初始值,以防止下面所述的情况:
 | 
			
		||||
            //
 | 
			
		||||
            // 1. 更改 `linkageRules`,使 a 字段满足条件时被隐藏
 | 
			
		||||
            // 2. 设置表单,使其满足条件,进而隐藏 a 字段
 | 
			
		||||
            // 3. 再次更改 `linkageRules`,使 a 字段满足条件时被禁用
 | 
			
		||||
            // 4. 设置表单,使其满足条件,会发现 a 字段还是隐藏状态,这里期望的是只显示禁用状态
 | 
			
		||||
            onFieldInit(`*(${fields})`, (field: any) => {
 | 
			
		||||
              Object.keys(field).forEach((key) => {
 | 
			
		||||
                if (key.startsWith('_') && field[key] !== undefined) {
 | 
			
		||||
                  field[key.slice(1)] = field[key];
 | 
			
		||||
                }
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            // `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 代替
 | 
			
		||||
            onFieldInit(`*(${fields})`, (field: any, form) => {
 | 
			
		||||
              disposes.push(
 | 
			
		||||
                autorun(() => {
 | 
			
		||||
                  linkageMergeAction({
 | 
			
		||||
                    operator: h.operator,
 | 
			
		||||
                    value: h.value,
 | 
			
		||||
                    field,
 | 
			
		||||
                    condition: v.condition,
 | 
			
		||||
                    values: form?.values,
 | 
			
		||||
                    variables,
 | 
			
		||||
                    localVariables,
 | 
			
		||||
                  });
 | 
			
		||||
    return () => {
 | 
			
		||||
      form.removeEffects(id);
 | 
			
		||||
    };
 | 
			
		||||
  }, []);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const id = uid();
 | 
			
		||||
    const linkagefields = [];
 | 
			
		||||
    form.addEffects(id, () => {
 | 
			
		||||
      return linkageRules.map((v, index) => {
 | 
			
		||||
        return v.actions?.map((h) => {
 | 
			
		||||
          if (h.targetFields) {
 | 
			
		||||
            const fields = h.targetFields.join(',');
 | 
			
		||||
            return onFieldReact(`*(${fields})`, (field: any, form) => {
 | 
			
		||||
              linkagefields.push(field);
 | 
			
		||||
              linkageMergeAction(h, field, v.condition, form?.values);
 | 
			
		||||
              if (index === linkageRules.length - 1) {
 | 
			
		||||
                setTimeout(() =>
 | 
			
		||||
                  linkagefields.map((v) => {
 | 
			
		||||
                    v.linkageProperty = {};
 | 
			
		||||
                }),
 | 
			
		||||
              );
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      form.removeEffects(id);
 | 
			
		||||
      disposes.forEach((dispose) => {
 | 
			
		||||
        dispose();
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
  }, [linkageRules]);
 | 
			
		||||
  }, [JSON.stringify(linkageRules)]);
 | 
			
		||||
 | 
			
		||||
  return fieldSchema['x-decorator'] === 'FormV2' ? <FormDecorator {...props} /> : <FormComponent {...props} />;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,83 +1,148 @@
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { evaluators } from '@nocobase/evaluators/client';
 | 
			
		||||
import { cloneDeep, last } from 'lodash';
 | 
			
		||||
import { uid } from '@nocobase/utils';
 | 
			
		||||
import _, { cloneDeep } from 'lodash';
 | 
			
		||||
import { ActionType } from '../../../schema-settings/LinkageRules/type';
 | 
			
		||||
import { conditionAnalyse } from '../../common/utils/uitls';
 | 
			
		||||
import { VariableOption, VariablesContextType } from '../../../variables/types';
 | 
			
		||||
import { REGEX_OF_VARIABLE } from '../../../variables/utils/isVariable';
 | 
			
		||||
import { conditionAnalyses } from '../../common/utils/uitls';
 | 
			
		||||
 | 
			
		||||
export const linkageMergeAction = ({ operator, value }, field, condition, values) => {
 | 
			
		||||
  const requiredResult = field?.linkageProperty?.required || [field?.initProperty?.required || false];
 | 
			
		||||
  const displayResult = field?.linkageProperty?.display || [field?.initProperty?.display];
 | 
			
		||||
  const patternResult = field?.linkageProperty?.pattern || [field?.initProperty?.pattern];
 | 
			
		||||
  const valueResult = field?.linkageProperty?.value || [field.value || field?.initProperty?.value];
 | 
			
		||||
interface Props {
 | 
			
		||||
  operator;
 | 
			
		||||
  value;
 | 
			
		||||
  field: Field & {
 | 
			
		||||
    [key: string]: any;
 | 
			
		||||
  };
 | 
			
		||||
  condition;
 | 
			
		||||
  values;
 | 
			
		||||
  variables: VariablesContextType;
 | 
			
		||||
  localVariables: VariableOption[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const linkageMergeAction = async ({
 | 
			
		||||
  operator,
 | 
			
		||||
  value,
 | 
			
		||||
  field,
 | 
			
		||||
  condition,
 | 
			
		||||
  values,
 | 
			
		||||
  variables,
 | 
			
		||||
  localVariables,
 | 
			
		||||
}: Props) => {
 | 
			
		||||
  const { evaluate } = evaluators.get('formula.js');
 | 
			
		||||
 | 
			
		||||
  switch (operator) {
 | 
			
		||||
    case ActionType.Required:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        requiredResult.push(true);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field.required = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.required = false;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        required: requiredResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.required = last(field.linkageProperty?.required);
 | 
			
		||||
      break;
 | 
			
		||||
    case ActionType.InRequired:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        requiredResult.push(false);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field.required = false;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.required = true;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        required: requiredResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.required = last(field.linkageProperty?.required);
 | 
			
		||||
      break;
 | 
			
		||||
    case ActionType.Visible:
 | 
			
		||||
    case ActionType.None:
 | 
			
		||||
    case ActionType.Hidden:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        displayResult.push(operator);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field._display = '_display' in field ? field._display : field.display;
 | 
			
		||||
        field.display = operator;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.display = field._display;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        display: displayResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.display = last(field.linkageProperty?.display);
 | 
			
		||||
      break;
 | 
			
		||||
    case ActionType.Editable:
 | 
			
		||||
    case ActionType.ReadOnly:
 | 
			
		||||
    case ActionType.ReadPretty:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
        patternResult.push(operator);
 | 
			
		||||
      if (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables })) {
 | 
			
		||||
        field._pattern = '_pattern' in field ? field._pattern : field.pattern;
 | 
			
		||||
        field.pattern = operator;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.pattern = field._pattern;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        pattern: patternResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.pattern = last(field.linkageProperty.pattern);
 | 
			
		||||
      break;
 | 
			
		||||
    case ActionType.Value:
 | 
			
		||||
      if (conditionAnalyse(condition, values)) {
 | 
			
		||||
      if (
 | 
			
		||||
        isConditionEmpty(condition) ||
 | 
			
		||||
        (await conditionAnalyses({ rules: condition, formValues: values, variables, localVariables }))
 | 
			
		||||
      ) {
 | 
			
		||||
        let result = null;
 | 
			
		||||
        if (value?.mode === 'express') {
 | 
			
		||||
          const scope = cloneDeep(values);
 | 
			
		||||
 | 
			
		||||
          // 1. 解析如 `{{$user.name}}` 之类的变量
 | 
			
		||||
          const { exp, scope: expScope } = await replaceVariables(value.value || value.result, {
 | 
			
		||||
            variables,
 | 
			
		||||
            localVariables,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          try {
 | 
			
		||||
            const result = evaluate(value.result || value.value, { ...scope, now: () => new Date().toString() });
 | 
			
		||||
            valueResult.push(result);
 | 
			
		||||
            // 2. TODO: 需要把里面解析变量的逻辑删除,因为在上一步已经解析过了
 | 
			
		||||
            result = evaluate(exp, { ...scope, now: () => new Date().toString(), ...expScope });
 | 
			
		||||
          } catch (error) {
 | 
			
		||||
            console.log(error);
 | 
			
		||||
            if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
              throw error;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else if (value?.mode === 'constant') {
 | 
			
		||||
          valueResult.push(value?.value || value);
 | 
			
		||||
          result = value?.value || value;
 | 
			
		||||
        } else {
 | 
			
		||||
          valueResult.push(null);
 | 
			
		||||
          result = null;
 | 
			
		||||
        }
 | 
			
		||||
        field._value = '_value' in field ? field._value : field.value;
 | 
			
		||||
        field.value = result === undefined ? field.value : result;
 | 
			
		||||
      } else {
 | 
			
		||||
        field.value = field._value;
 | 
			
		||||
      }
 | 
			
		||||
      field.linkageProperty = {
 | 
			
		||||
        ...field.linkageProperty,
 | 
			
		||||
        value: valueResult,
 | 
			
		||||
      };
 | 
			
		||||
      field.value = last(valueResult) === undefined ? field.value : last(valueResult);
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      return null;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
async function replaceVariables(
 | 
			
		||||
  value: string,
 | 
			
		||||
  {
 | 
			
		||||
    variables,
 | 
			
		||||
    localVariables,
 | 
			
		||||
  }: {
 | 
			
		||||
    variables: VariablesContextType;
 | 
			
		||||
    localVariables: VariableOption[];
 | 
			
		||||
  },
 | 
			
		||||
) {
 | 
			
		||||
  const store = {};
 | 
			
		||||
  const scope = {};
 | 
			
		||||
 | 
			
		||||
  const waitForParsing = value.match(REGEX_OF_VARIABLE)?.map(async (item) => {
 | 
			
		||||
    const result = await variables.parseVariable(item, localVariables);
 | 
			
		||||
 | 
			
		||||
    // 在开头加 `_` 是为了保证 id 不能以数字开头,否则在解析表达式的时候(不是解析变量)会报错
 | 
			
		||||
    const id = `_${uid()}`;
 | 
			
		||||
 | 
			
		||||
    scope[id] = result;
 | 
			
		||||
    store[item] = id;
 | 
			
		||||
    return result;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (waitForParsing) {
 | 
			
		||||
    await Promise.all(waitForParsing);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    exp: value.replace(REGEX_OF_VARIABLE, (match) => {
 | 
			
		||||
      return store[match] || match;
 | 
			
		||||
    }),
 | 
			
		||||
    scope,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function isConditionEmpty(rules: { $and?: any; $or?: any }) {
 | 
			
		||||
  const type = Object.keys(rules)[0] || '$and';
 | 
			
		||||
  const conditions = rules[type];
 | 
			
		||||
 | 
			
		||||
  return _.isEmpty(conditions);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,18 +13,24 @@ describe('Form', () => {
 | 
			
		||||
  it('basic', async () => {
 | 
			
		||||
    render(<App2 />);
 | 
			
		||||
 | 
			
		||||
    await sleep();
 | 
			
		||||
 | 
			
		||||
    const submit = screen.getByText('Submit');
 | 
			
		||||
    const input = document.querySelector('.ant-input') as HTMLInputElement;
 | 
			
		||||
 | 
			
		||||
    await waitFor(() => {
 | 
			
		||||
      expect(submit).toBeInTheDocument();
 | 
			
		||||
      expect(input).toBeInTheDocument();
 | 
			
		||||
    expect(screen.getByText(/form title/i)).toBeInTheDocument();
 | 
			
		||||
    expect(screen.getByText('T1')).toBeInTheDocument();
 | 
			
		||||
    expect(screen.getByText(/\{\}/i)).toBeInTheDocument();
 | 
			
		||||
      expect(screen.queryByText(/form title/i)).toBeInTheDocument();
 | 
			
		||||
      expect(screen.queryByText('T1')).toBeInTheDocument();
 | 
			
		||||
      expect(screen.queryByText(/\{\}/i)).toBeInTheDocument();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // 使用 waitFor 防止报错,参见:https://github.com/testing-library/user-event/issues/662#issuecomment-904365493
 | 
			
		||||
    await waitFor(() => userEvent.type(input, '123'));
 | 
			
		||||
    expect(screen.getByText(/\{ "field1": "123" \}/i)).toBeInTheDocument();
 | 
			
		||||
    await waitFor(() => {
 | 
			
		||||
      expect(screen.queryByText(/\{ "field1": "123" \}/i)).toBeInTheDocument();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('decorator', async () => {
 | 
			
		||||
@ -46,7 +52,9 @@ describe('Form', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await userEvent.type(input, '123');
 | 
			
		||||
    expect(screen.getByText(/\{ "field1": "aaa123" \}/i)).toBeInTheDocument();
 | 
			
		||||
    await waitFor(() => {
 | 
			
		||||
      expect(screen.queryByText(/\{ "field1": "aaa123" \}/i)).toBeInTheDocument();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('Form & Drawer', async () => {
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,9 @@
 | 
			
		||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCompile, useDesignable } from '../..';
 | 
			
		||||
import { useGanttBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useFormBlockContext, useGanttBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollection } from '../../../collection-manager';
 | 
			
		||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
 | 
			
		||||
@ -25,14 +24,13 @@ const useOptions = (type = 'string') => {
 | 
			
		||||
export const GanttDesigner = () => {
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { name, title, fields } = useCollection();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const { name, title } = useCollection();
 | 
			
		||||
  const { service } = useGanttBlockContext();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const template = useSchemaTemplate();
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const fieldNames = fieldSchema?.['x-decorator-props']?.['fieldNames'] || {};
 | 
			
		||||
  const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
 | 
			
		||||
  return (
 | 
			
		||||
@ -146,28 +144,10 @@ export const GanttDesigner = () => {
 | 
			
		||||
          dn.refresh();
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {},
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
        initialValues={
 | 
			
		||||
          {
 | 
			
		||||
            // title: field.title,
 | 
			
		||||
            // icon: field.componentProps.icon,
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={name}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          const params = field.decoratorProps.params || {};
 | 
			
		||||
          params.filter = filter;
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,13 @@ import { Slider } from 'antd';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import React, { useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCollection, useCollectionFilterOptions, useSortFields } from '../../../collection-manager';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollection, useSortFields } from '../../../collection-manager';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
import { SchemaComponentOptions } from '../../core';
 | 
			
		||||
import { useDesignable } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
 | 
			
		||||
import { defaultColumnCount, gridSizes, pageSizeOptions, screenSizeMaps, screenSizeTitleMaps } from './options';
 | 
			
		||||
 | 
			
		||||
const columnCountMarks = [1, 2, 3, 4, 6, 8, 12, 24].reduce((obj, cur) => {
 | 
			
		||||
@ -23,11 +23,10 @@ export const GridCardDesigner = () => {
 | 
			
		||||
  const template = useSchemaTemplate();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const sortFields = useSortFields(name);
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
 | 
			
		||||
  const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
 | 
			
		||||
  const columnCount = field.decoratorProps.columnCount || defaultColumnCount;
 | 
			
		||||
@ -94,25 +93,10 @@ export const GridCardDesigner = () => {
 | 
			
		||||
            });
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <SchemaSettings.ModalItem
 | 
			
		||||
          title={t('Set the data scope')}
 | 
			
		||||
          schema={
 | 
			
		||||
            {
 | 
			
		||||
              type: 'object',
 | 
			
		||||
              title: t('Set the data scope'),
 | 
			
		||||
              properties: {
 | 
			
		||||
                filter: {
 | 
			
		||||
                  default: defaultFilter,
 | 
			
		||||
                  // title: '数据范围',
 | 
			
		||||
                  enum: dataSource,
 | 
			
		||||
                  'x-component': 'Filter',
 | 
			
		||||
                  'x-component-props': {
 | 
			
		||||
                    dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            } as ISchema
 | 
			
		||||
          }
 | 
			
		||||
        <SchemaSettings.DataScope
 | 
			
		||||
          collectionName={name}
 | 
			
		||||
          defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
          form={form}
 | 
			
		||||
          onSubmit={({ filter }) => {
 | 
			
		||||
            filter = removeNullCondition(filter);
 | 
			
		||||
            _.set(fieldSchema, 'x-decorator-props.params.filter', filter);
 | 
			
		||||
 | 
			
		||||
@ -1,49 +1,30 @@
 | 
			
		||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useKanbanBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useFormBlockContext, useKanbanBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollection } from '../../../collection-manager';
 | 
			
		||||
import { useCollectionFilterOptions } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
import { useDesignable } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FixedBlockDesignerItem } from '../page';
 | 
			
		||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
 | 
			
		||||
 | 
			
		||||
export const KanbanDesigner = () => {
 | 
			
		||||
  const { name, title } = useCollection();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const { service } = useKanbanBlockContext();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
 | 
			
		||||
  const template = useSchemaTemplate();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <GeneralSchemaDesigner template={template} title={title || name}>
 | 
			
		||||
      <SchemaSettings.BlockTitleItem />
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {
 | 
			
		||||
                  dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={name}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          filter = removeNullCondition(filter);
 | 
			
		||||
          const params = field.decoratorProps.params || {};
 | 
			
		||||
 | 
			
		||||
@ -3,25 +3,22 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCollection, useCollectionFilterOptions, useSortFields } from '../../../collection-manager';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollection, useSortFields } from '../../../collection-manager';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
import { useDesignable } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
 | 
			
		||||
import { useListBlockContext } from './List.Decorator';
 | 
			
		||||
 | 
			
		||||
export const ListDesigner = () => {
 | 
			
		||||
  const { name, title } = useCollection();
 | 
			
		||||
  const template = useSchemaTemplate();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { service } = useListBlockContext();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const sortFields = useSortFields(name);
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
 | 
			
		||||
  const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
 | 
			
		||||
  const sort = defaultSort?.map((item: string) => {
 | 
			
		||||
@ -38,25 +35,10 @@ export const ListDesigner = () => {
 | 
			
		||||
  return (
 | 
			
		||||
    <GeneralSchemaDesigner template={template} title={title || name}>
 | 
			
		||||
      <SchemaSettings.BlockTitleItem />
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                // title: '数据范围',
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {
 | 
			
		||||
                  dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={name}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          filter = removeNullCondition(filter);
 | 
			
		||||
          _.set(fieldSchema, 'x-decorator-props.params.filter', filter);
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import { LoadingOutlined } from '@ant-design/icons';
 | 
			
		||||
import { connect, mapProps, mapReadPretty, useFieldSchema } from '@formily/react';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import { Divider, SelectProps, Tag } from 'antd';
 | 
			
		||||
import _, { uniqBy } from 'lodash';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import { uniqBy } from 'lodash';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
 | 
			
		||||
import { ResourceActionOptions, useRequest } from '../../../api-client';
 | 
			
		||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
 | 
			
		||||
@ -127,7 +127,7 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        manual: manual && Object.prototype.toString.call(value) === '[object Object]',
 | 
			
		||||
        manual,
 | 
			
		||||
        debounceWait: wait,
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
@ -194,6 +194,7 @@ const InternalRemoteSelect = connect(
 | 
			
		||||
      }
 | 
			
		||||
      firstRun.current = true;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <Select
 | 
			
		||||
        open={open}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { connect, mapReadPretty, mapProps } from '@formily/react';
 | 
			
		||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import ReactQuill from 'react-quill';
 | 
			
		||||
import { isVariable } from '../../../variables/utils/isVariable';
 | 
			
		||||
import { ReadPretty as InputReadPretty } from '../input';
 | 
			
		||||
import { useStyles } from './style';
 | 
			
		||||
 | 
			
		||||
@ -23,24 +24,22 @@ export const RichText = connect(
 | 
			
		||||
      'link',
 | 
			
		||||
      'image',
 | 
			
		||||
    ];
 | 
			
		||||
    const { value, onChange, disabled } = props;
 | 
			
		||||
    const { value, defaultValue, onChange, disabled } = props;
 | 
			
		||||
    const resultValue = isVariable(value || defaultValue) ? undefined : value || defaultValue || '';
 | 
			
		||||
 | 
			
		||||
    return wrapSSR(
 | 
			
		||||
      <ReactQuill
 | 
			
		||||
        className={`${componentCls} ${hashId}`}
 | 
			
		||||
        modules={modules}
 | 
			
		||||
        formats={formats}
 | 
			
		||||
        value={typeof value === 'string' ? value : ''}
 | 
			
		||||
        value={resultValue}
 | 
			
		||||
        onChange={onChange}
 | 
			
		||||
        readOnly={disabled}
 | 
			
		||||
      />,
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
  mapProps((props) => {
 | 
			
		||||
    const { value } = props;
 | 
			
		||||
    return {
 | 
			
		||||
      ...props,
 | 
			
		||||
      value: typeof value === 'object' ? value?.default : value,
 | 
			
		||||
    };
 | 
			
		||||
  mapProps({
 | 
			
		||||
    initialValue: 'defaultValue',
 | 
			
		||||
  }),
 | 
			
		||||
  mapReadPretty((props) => {
 | 
			
		||||
    return <InputReadPretty.Html {...props} />;
 | 
			
		||||
 | 
			
		||||
@ -1,14 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
 | 
			
		||||
import { Variable } from '../variable';
 | 
			
		||||
 | 
			
		||||
export function FilterDynamicComponent(props) {
 | 
			
		||||
  const { value, onChange, renderSchemaComponent, form, collectionField, ...other } = props;
 | 
			
		||||
  const options = useVariableOptions({ form, collectionField, ...other });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Variable.Input value={value} onChange={onChange} scope={options}>
 | 
			
		||||
      {renderSchemaComponent()}
 | 
			
		||||
    </Variable.Input>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -2,13 +2,13 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { set } from 'lodash';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { ArrayItems } from '@formily/antd-v5';
 | 
			
		||||
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled, isShowDefaultValue } from '../../../schema-settings';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings, isPatternDisabled } from '../../../schema-settings';
 | 
			
		||||
import useIsAllowToSetDefaultValue from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
 | 
			
		||||
import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks';
 | 
			
		||||
import { useAssociationFieldContext } from '../association-field/hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
 | 
			
		||||
 | 
			
		||||
const useLabelFields = (collectionName?: any) => {
 | 
			
		||||
  // 需要在组件顶层调用
 | 
			
		||||
@ -46,6 +46,7 @@ export const useColorFields = (collectionName?: any) => {
 | 
			
		||||
};
 | 
			
		||||
export const TableColumnDesigner = (props) => {
 | 
			
		||||
  const { uiSchema, fieldSchema, collectionField } = props;
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const { getInterface, getCollection } = useCollectionManager();
 | 
			
		||||
  const field: any = useField();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
@ -55,13 +56,13 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
    fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames'];
 | 
			
		||||
  const options = useLabelFields(collectionField?.target ?? collectionField?.targetCollection);
 | 
			
		||||
  const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection);
 | 
			
		||||
  const intefaceCfg = getInterface(collectionField?.interface);
 | 
			
		||||
  const interfaceCfg = getInterface(collectionField?.interface);
 | 
			
		||||
  const targetCollection = getCollection(collectionField?.target);
 | 
			
		||||
  const isFileField = isFileCollection(targetCollection);
 | 
			
		||||
  const isSubTableColumn = ['QuickEdit', 'FormItem'].includes(fieldSchema['x-decorator']);
 | 
			
		||||
  const { currentMode } = useAssociationFieldContext();
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(collectionField?.target);
 | 
			
		||||
  const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue({ collectionField, fieldSchema });
 | 
			
		||||
 | 
			
		||||
  const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
 | 
			
		||||
  const isAssociationField = ['obo', 'oho', 'o2o', 'o2m', 'm2m', 'm2o', 'snapshot'].includes(
 | 
			
		||||
    collectionField?.interface,
 | 
			
		||||
@ -76,6 +77,7 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
    readOnlyMode = 'read-pretty';
 | 
			
		||||
  }
 | 
			
		||||
  const isSelectFieldMode = isAssociationField && fieldMode === 'Select';
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <GeneralSchemaDesigner disableInitializer>
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
@ -111,24 +113,10 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      {isSelectFieldMode && !field.readPretty && !uiSchema?.['x-read-pretty'] && isSubTableColumn && (
 | 
			
		||||
        <SchemaSettings.ModalItem
 | 
			
		||||
          title={t('Set the data scope')}
 | 
			
		||||
          schema={
 | 
			
		||||
            {
 | 
			
		||||
              type: 'object',
 | 
			
		||||
              title: t('Set the data scope'),
 | 
			
		||||
              properties: {
 | 
			
		||||
                filter: {
 | 
			
		||||
                  default: defaultFilter,
 | 
			
		||||
                  enum: dataSource,
 | 
			
		||||
                  'x-component': 'Filter',
 | 
			
		||||
                  'x-component-props': {
 | 
			
		||||
                    dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            } as ISchema
 | 
			
		||||
          }
 | 
			
		||||
        <SchemaSettings.DataScope
 | 
			
		||||
          collectionName={collectionField?.target}
 | 
			
		||||
          defaultFilter={fieldSchema?.['x-component-props']?.service?.params?.filter || {}}
 | 
			
		||||
          form={form}
 | 
			
		||||
          onSubmit={({ filter }) => {
 | 
			
		||||
            filter = removeNullCondition(filter);
 | 
			
		||||
            set(field.componentProps, 'service.params.filter', filter);
 | 
			
		||||
@ -178,7 +166,7 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
          dn.refresh();
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      {intefaceCfg && intefaceCfg.sortable === true && !currentMode && (
 | 
			
		||||
      {interfaceCfg && interfaceCfg.sortable === true && !currentMode && (
 | 
			
		||||
        <SchemaSettings.SwitchItem
 | 
			
		||||
          title={t('Sortable')}
 | 
			
		||||
          checked={field.componentProps.sorter}
 | 
			
		||||
@ -407,9 +395,7 @@ export const TableColumnDesigner = (props) => {
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
      {isSubTableColumn && !field?.readPretty && isShowDefaultValue(collectionField, getInterface) && (
 | 
			
		||||
        <SchemaSettings.DefaultValue fieldSchema={fieldSchema} />
 | 
			
		||||
      )}
 | 
			
		||||
      {isAllowToSetDefaultValue(isSubTableColumn) && <SchemaSettings.DefaultValue fieldSchema={fieldSchema} />}
 | 
			
		||||
      <SchemaSettings.Divider />
 | 
			
		||||
      <SchemaSettings.Remove
 | 
			
		||||
        removeParentsIfNoChildren={!isSubTableColumn}
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,11 @@ import { DeleteOutlined, MenuOutlined } from '@ant-design/icons';
 | 
			
		||||
import { TinyColor } from '@ctrl/tinycolor';
 | 
			
		||||
import { SortableContext, SortableContextProps, useSortable } from '@dnd-kit/sortable';
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { ArrayField, Field } from '@formily/core';
 | 
			
		||||
import { ArrayField } from '@formily/core';
 | 
			
		||||
import { spliceArrayState } from '@formily/core/esm/shared/internals';
 | 
			
		||||
import { RecursionField, Schema, observer, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { action, reaction } from '@formily/reactive';
 | 
			
		||||
import { action } from '@formily/reactive';
 | 
			
		||||
import { isPortalInBody } from '@nocobase/utils/client';
 | 
			
		||||
import { useMemoizedFn } from 'ahooks';
 | 
			
		||||
import { Table as AntdTable, TableColumnProps } from 'antd';
 | 
			
		||||
import { default as classNames, default as cls } from 'classnames';
 | 
			
		||||
@ -20,16 +21,17 @@ import {
 | 
			
		||||
  useTableSelectorContext,
 | 
			
		||||
} from '../../../';
 | 
			
		||||
import { useACLFieldWhitelist } from '../../../acl/ACLProvider';
 | 
			
		||||
import { LocalVariablesProvider } from '../../../variables/hooks/useLocalVariables';
 | 
			
		||||
import { useToken } from '../__builtins__';
 | 
			
		||||
import { ColumnFieldProvider } from './components/ColumnFieldProvider';
 | 
			
		||||
import { extractIndex, isCollectionFieldComponent, isColumnComponent, isPortalInBody } from './utils';
 | 
			
		||||
import { extractIndex, isCollectionFieldComponent, isColumnComponent } from './utils';
 | 
			
		||||
 | 
			
		||||
const useArrayField = (props) => {
 | 
			
		||||
  const field = useField<ArrayField>();
 | 
			
		||||
  return (props.field || field) as ArrayField;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const useTableColumns = (props) => {
 | 
			
		||||
const useTableColumns = (props: { showDel?: boolean; isSubTable?: boolean }) => {
 | 
			
		||||
  const field = useArrayField(props);
 | 
			
		||||
  const schema = useFieldSchema();
 | 
			
		||||
  const { schemaInWhitelist } = useACLFieldWhitelist();
 | 
			
		||||
@ -60,6 +62,7 @@ const useTableColumns = (props) => {
 | 
			
		||||
          const index = field.value?.indexOf(record);
 | 
			
		||||
          return (
 | 
			
		||||
            <RecordIndexProvider index={record.__index || index}>
 | 
			
		||||
              <LocalVariablesProvider iterationCtx={record}>
 | 
			
		||||
                <RecordProvider record={record}>
 | 
			
		||||
                  <ColumnFieldProvider schema={s} basePath={field.address.concat(record.__index || index)}>
 | 
			
		||||
                    <RecursionField
 | 
			
		||||
@ -69,6 +72,7 @@ const useTableColumns = (props) => {
 | 
			
		||||
                    />
 | 
			
		||||
                  </ColumnFieldProvider>
 | 
			
		||||
                </RecordProvider>
 | 
			
		||||
              </LocalVariablesProvider>
 | 
			
		||||
            </RecordIndexProvider>
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
@ -97,7 +101,7 @@ const useTableColumns = (props) => {
 | 
			
		||||
          <DeleteOutlined
 | 
			
		||||
            style={{ cursor: 'pointer' }}
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              action(() => {
 | 
			
		||||
              void action(() => {
 | 
			
		||||
                spliceArrayState(field as any, {
 | 
			
		||||
                  startIndex: index,
 | 
			
		||||
                  deleteCount: 1,
 | 
			
		||||
@ -161,7 +165,11 @@ const SortHandle = (props) => {
 | 
			
		||||
const TableIndex = (props) => {
 | 
			
		||||
  const { index } = props;
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={classNames('nb-table-index')} style={{ padding: '0 8px 0 16px' }}>
 | 
			
		||||
    <div
 | 
			
		||||
      data-testid={`table-index-${index}`}
 | 
			
		||||
      className={classNames('nb-table-index')}
 | 
			
		||||
      style={{ padding: '0 8px 0 16px' }}
 | 
			
		||||
    >
 | 
			
		||||
      {index}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
@ -184,28 +192,22 @@ const usePaginationProps = (pagination1, pagination2) => {
 | 
			
		||||
  return result.total <= result.pageSize ? false : result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const useValidator = (validator: (value: any) => string) => {
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const dispose = reaction(
 | 
			
		||||
      () => field.value,
 | 
			
		||||
      (value) => {
 | 
			
		||||
        const message = validator(value);
 | 
			
		||||
        field.setFeedback({
 | 
			
		||||
          type: 'error',
 | 
			
		||||
          code: 'ValidateError',
 | 
			
		||||
          messages: message ? [message] : [],
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
    return () => {
 | 
			
		||||
      dispose();
 | 
			
		||||
    };
 | 
			
		||||
  }, []);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const Table: any = observer(
 | 
			
		||||
  (props: any) => {
 | 
			
		||||
  (props: {
 | 
			
		||||
    useProps?: () => any;
 | 
			
		||||
    onChange?: (pagination, filters, sorter, extra) => void;
 | 
			
		||||
    onRowSelectionChange?: (selectedRowKeys: any[], selectedRows: any[]) => void;
 | 
			
		||||
    onRowDragEnd?: (e: { from: any; to: any }) => void;
 | 
			
		||||
    onClickRow?: (record: any, setSelectedRow: (selectedRow: any[]) => void, selectedRow: any[]) => void;
 | 
			
		||||
    pagination?: any;
 | 
			
		||||
    showIndex?: boolean;
 | 
			
		||||
    dragSort?: boolean;
 | 
			
		||||
    rowKey?: string | ((record: any) => string);
 | 
			
		||||
    rowSelection?: any;
 | 
			
		||||
    required?: boolean;
 | 
			
		||||
    onExpand?: (flag: boolean, record: any) => void;
 | 
			
		||||
    isSubTable?: boolean;
 | 
			
		||||
  }) => {
 | 
			
		||||
    const { token } = useToken();
 | 
			
		||||
    const { pagination: pagination1, useProps, onChange, ...others1 } = props;
 | 
			
		||||
    const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {};
 | 
			
		||||
@ -306,7 +308,7 @@ export const Table: any = observer(
 | 
			
		||||
                  const toIndex = e.over?.data.current?.sortable?.index;
 | 
			
		||||
                  const from = field.value[fromIndex] || e.active;
 | 
			
		||||
                  const to = field.value[toIndex] || e.over;
 | 
			
		||||
                  field.move(fromIndex, toIndex);
 | 
			
		||||
                  void field.move(fromIndex, toIndex);
 | 
			
		||||
                  onRowDragEnd({ from, to });
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
 | 
			
		||||
@ -1,31 +1,28 @@
 | 
			
		||||
import { ArrayItems } from '@formily/antd-v5';
 | 
			
		||||
import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import React, { useCallback, useMemo } from 'react';
 | 
			
		||||
import React, { useCallback } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useTableBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useFormBlockContext, useTableBlockContext } from '../../../block-provider';
 | 
			
		||||
import { mergeFilter } from '../../../block-provider/SharedFilterProvider';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { useSortFields } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { FilterBlockType } from '../../../filter-provider/utils';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
import { useCompile, useDesignable } from '../../hooks';
 | 
			
		||||
import { useDesignable } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FixedBlockDesignerItem } from '../page';
 | 
			
		||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
 | 
			
		||||
 | 
			
		||||
export const TableBlockDesigner = () => {
 | 
			
		||||
  const { name, title, sortable } = useCollection();
 | 
			
		||||
  const { getCollectionField, getCollection } = useCollectionManager();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const sortFields = useSortFields(name);
 | 
			
		||||
  const { service } = useTableBlockContext();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
 | 
			
		||||
  const defaultResource = fieldSchema?.['x-decorator-props']?.resource;
 | 
			
		||||
  const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate;
 | 
			
		||||
@ -45,23 +42,6 @@ export const TableBlockDesigner = () => {
 | 
			
		||||
  const { dragSort, resource } = field.decoratorProps;
 | 
			
		||||
  const collectionField = resource && getCollectionField(resource);
 | 
			
		||||
  const treeCollection = resource?.includes('.') ? getCollection(collectionField?.target)?.tree : !!collection?.tree;
 | 
			
		||||
  const dataScopeSchema = useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      type: 'object',
 | 
			
		||||
      title: t('Set the data scope'),
 | 
			
		||||
      properties: {
 | 
			
		||||
        filter: {
 | 
			
		||||
          default: defaultFilter,
 | 
			
		||||
          // title: '数据范围',
 | 
			
		||||
          enum: compile(dataSource),
 | 
			
		||||
          'x-component': 'Filter',
 | 
			
		||||
          'x-component-props': {
 | 
			
		||||
            dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    } as ISchema;
 | 
			
		||||
  }, [dataSource, defaultFilter]);
 | 
			
		||||
  const onDataScopeSubmit = useCallback(
 | 
			
		||||
    ({ filter }) => {
 | 
			
		||||
      filter = removeNullCondition(filter);
 | 
			
		||||
@ -81,7 +61,7 @@ export const TableBlockDesigner = () => {
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [field],
 | 
			
		||||
    [dn, field.decoratorProps, fieldSchema, service],
 | 
			
		||||
  );
 | 
			
		||||
  return (
 | 
			
		||||
    <GeneralSchemaDesigner template={template} title={title || name}>
 | 
			
		||||
@ -124,7 +104,12 @@ export const TableBlockDesigner = () => {
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      <FixedBlockDesignerItem />
 | 
			
		||||
      <SchemaSettings.ModalItem title={t('Set the data scope')} schema={dataScopeSchema} onSubmit={onDataScopeSubmit} />
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={name}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={onDataScopeSubmit}
 | 
			
		||||
      />
 | 
			
		||||
      {!dragSort && (
 | 
			
		||||
        <SchemaSettings.ModalItem
 | 
			
		||||
          title={t('Set default sorting rules')}
 | 
			
		||||
 | 
			
		||||
@ -3,27 +3,25 @@ import { ISchema, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { cloneDeep } from 'lodash';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useTableSelectorContext } from '../../../block-provider';
 | 
			
		||||
import { useFormBlockContext, useTableSelectorContext } from '../../../block-provider';
 | 
			
		||||
import { recursiveParent } from '../../../block-provider/TableSelectorProvider';
 | 
			
		||||
import { useCollection, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { useCollectionFilterOptions, useSortFields } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { useSortFields } from '../../../collection-manager/action-hooks';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../../../schema-settings';
 | 
			
		||||
import { useSchemaTemplate } from '../../../schema-templates';
 | 
			
		||||
import { useDesignable } from '../../hooks';
 | 
			
		||||
import { removeNullCondition } from '../filter';
 | 
			
		||||
import { FilterDynamicComponent } from './FilterDynamicComponent';
 | 
			
		||||
import { recursiveParent } from '../../../block-provider/TableSelectorProvider';
 | 
			
		||||
 | 
			
		||||
export const TableSelectorDesigner = () => {
 | 
			
		||||
  const { name, title } = useCollection();
 | 
			
		||||
  const { getCollectionJoinField } = useCollectionManager();
 | 
			
		||||
  const field = useField();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const dataSource = useCollectionFilterOptions(name);
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const sortFields = useSortFields(name);
 | 
			
		||||
  const { service, extraFilter } = useTableSelectorContext();
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const { dn } = useDesignable();
 | 
			
		||||
  const defaultFilter = fieldSchema?.['x-decorator-props']?.params?.filter || {};
 | 
			
		||||
  const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || [];
 | 
			
		||||
  const collectionFieldSchema = recursiveParent(fieldSchema, 'CollectionField');
 | 
			
		||||
  const collectionField = getCollectionJoinField(collectionFieldSchema?.['x-collection-field']);
 | 
			
		||||
@ -43,25 +41,10 @@ export const TableSelectorDesigner = () => {
 | 
			
		||||
  const { dragSort } = field.decoratorProps;
 | 
			
		||||
  return (
 | 
			
		||||
    <GeneralSchemaDesigner template={template} title={title || name} disableInitializer>
 | 
			
		||||
      <SchemaSettings.ModalItem
 | 
			
		||||
        title={t('Set the data scope')}
 | 
			
		||||
        schema={
 | 
			
		||||
          {
 | 
			
		||||
            type: 'object',
 | 
			
		||||
            title: t('Set the data scope'),
 | 
			
		||||
            properties: {
 | 
			
		||||
              filter: {
 | 
			
		||||
                default: defaultFilter,
 | 
			
		||||
                // title: '数据范围',
 | 
			
		||||
                enum: dataSource,
 | 
			
		||||
                'x-component': 'Filter',
 | 
			
		||||
                'x-component-props': {
 | 
			
		||||
                  dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          } as ISchema
 | 
			
		||||
        }
 | 
			
		||||
      <SchemaSettings.DataScope
 | 
			
		||||
        collectionName={name}
 | 
			
		||||
        defaultFilter={fieldSchema?.['x-decorator-props']?.params?.filter || {}}
 | 
			
		||||
        form={form}
 | 
			
		||||
        onSubmit={({ filter }) => {
 | 
			
		||||
          filter = removeNullCondition(filter);
 | 
			
		||||
          const params = field.decoratorProps.params || {};
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,6 @@ import { TableSelector } from './TableSelector';
 | 
			
		||||
export * from './TableBlockDesigner';
 | 
			
		||||
export * from './TableField';
 | 
			
		||||
export * from './TableSelectorDesigner';
 | 
			
		||||
export * from './FilterDynamicComponent';
 | 
			
		||||
 | 
			
		||||
export const TableV2 = Table;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,17 +17,3 @@ export function extractIndex(str) {
 | 
			
		||||
  });
 | 
			
		||||
  return numbers.join('.');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 判断一个 DOM 对象是否是由 createPortal 挂在到了 body 上
 | 
			
		||||
 * @param domNode DOM 对象
 | 
			
		||||
 */
 | 
			
		||||
export const isPortalInBody = (dom: HTMLElement) => {
 | 
			
		||||
  while (dom) {
 | 
			
		||||
    if (dom.id === 'root') {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    dom = dom.parentNode as HTMLElement;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -15,23 +15,6 @@ import { XButton } from './XButton';
 | 
			
		||||
import { useStyles } from './style';
 | 
			
		||||
 | 
			
		||||
const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/;
 | 
			
		||||
const groupClass = css`
 | 
			
		||||
  width: auto;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  &.ant-input-group-compact {
 | 
			
		||||
    display: flex;
 | 
			
		||||
  }
 | 
			
		||||
  .ant-input-disabled {
 | 
			
		||||
    .ant-tag {
 | 
			
		||||
      color: #bfbfbf;
 | 
			
		||||
      border-color: #d9d9d9;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .ant-input.null-value {
 | 
			
		||||
    width: 4em;
 | 
			
		||||
    min-width: 4em;
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
function parseValue(value: any): string | string[] {
 | 
			
		||||
  if (value == null) {
 | 
			
		||||
@ -217,20 +200,20 @@ export function Input(props) {
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const onSwitch = useCallback(
 | 
			
		||||
    (next) => {
 | 
			
		||||
    (next, optionPath: any[]) => {
 | 
			
		||||
      if (next[0] === '') {
 | 
			
		||||
        if (next[1]) {
 | 
			
		||||
          if (next[1] !== type) {
 | 
			
		||||
            onChange(ConstantTypes[next[1]]?.default ?? null);
 | 
			
		||||
            onChange(ConstantTypes[next[1]]?.default ?? null, optionPath);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          if (variable) {
 | 
			
		||||
            onChange(null);
 | 
			
		||||
            onChange(null, optionPath);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      onChange(`{{${next.join('.')}}}`);
 | 
			
		||||
      onChange(`{{${next.join('.')}}}`, optionPath);
 | 
			
		||||
    },
 | 
			
		||||
    [type, variable, onChange],
 | 
			
		||||
  );
 | 
			
		||||
@ -254,6 +237,13 @@ export function Input(props) {
 | 
			
		||||
            }
 | 
			
		||||
            prevOption = prevOption.children.find((item) => item[names.value] === key);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // 如果为空则说明相关字段已被删除
 | 
			
		||||
          // fix T-1565
 | 
			
		||||
          if (!prevOption) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          labels.push(prevOption[names.label]);
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
          error(err);
 | 
			
		||||
@ -270,7 +260,7 @@ export function Input(props) {
 | 
			
		||||
  const disabled = props.disabled || form.disabled;
 | 
			
		||||
 | 
			
		||||
  return wrapSSR(
 | 
			
		||||
    <Space.Compact style={style} className={classNames(groupClass, componentCls, hashId, className)}>
 | 
			
		||||
    <Space.Compact style={style} className={classNames(componentCls, hashId, className)}>
 | 
			
		||||
      {variable ? (
 | 
			
		||||
        <div
 | 
			
		||||
          className={cx(
 | 
			
		||||
@ -332,7 +322,7 @@ export function Input(props) {
 | 
			
		||||
          ) : null}
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : (
 | 
			
		||||
        children ?? <ConstantComponent value={value} onChange={onChange} />
 | 
			
		||||
        <div style={{ flex: 1 }}>{children ?? <ConstantComponent value={value} onChange={onChange} />}</div>
 | 
			
		||||
      )}
 | 
			
		||||
      {options.length > 1 && !disabled ? (
 | 
			
		||||
        <Cascader
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import React, { createContext, useContext } from 'react';
 | 
			
		||||
import { connect, mapReadPretty } from '@formily/react';
 | 
			
		||||
import React, { createContext, useContext } from 'react';
 | 
			
		||||
 | 
			
		||||
import { IField } from '../../../collection-manager';
 | 
			
		||||
import { Input } from './Input';
 | 
			
		||||
import { JSONInput } from './JSONInput';
 | 
			
		||||
import { RawTextArea } from './RawTextArea';
 | 
			
		||||
@ -10,11 +9,7 @@ import { TextArea } from './TextArea';
 | 
			
		||||
const VariableScopeContext = createContext([]);
 | 
			
		||||
 | 
			
		||||
export function VariableScopeProvider({ scope = [], children }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <VariableScopeContext.Provider value={scope}>
 | 
			
		||||
      {children}
 | 
			
		||||
    </VariableScopeContext.Provider>
 | 
			
		||||
  )
 | 
			
		||||
  return <VariableScopeContext.Provider value={scope}>{children}</VariableScopeContext.Provider>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useVariableScope() {
 | 
			
		||||
@ -34,7 +29,3 @@ Variable.RawTextArea = connect(RawTextArea);
 | 
			
		||||
Variable.JSON = connect(JSONInput);
 | 
			
		||||
 | 
			
		||||
export default Variable;
 | 
			
		||||
 | 
			
		||||
export function isInvariable(value: IField) {
 | 
			
		||||
  return !!value?.invariable;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ export function VariableSelect({ options, setOptions, onInsert, changeOnSelect =
 | 
			
		||||
 | 
			
		||||
  async function loadData(selectedOptions) {
 | 
			
		||||
    const option = selectedOptions[selectedOptions.length - 1];
 | 
			
		||||
    if (!option.children && !option.isLeaf && option.loadChildren) {
 | 
			
		||||
    if (!option.children?.length && !option.isLeaf && option.loadChildren) {
 | 
			
		||||
      await option.loadChildren(option);
 | 
			
		||||
      setOptions((prev) => [...prev]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,14 @@ export const useStyles = genStyleHook('nb-variable', (token) => {
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    [componentCls]: {
 | 
			
		||||
      width: 'auto',
 | 
			
		||||
      display: 'flex',
 | 
			
		||||
      '&.ant-input-group-compact': { display: 'flex' },
 | 
			
		||||
      '.ant-input-disabled': {
 | 
			
		||||
        '.ant-tag': { color: '#bfbfbf', borderColor: '#d9d9d9' },
 | 
			
		||||
      },
 | 
			
		||||
      '.ant-input.null-value': { width: '4em', minWidth: '4em' },
 | 
			
		||||
 | 
			
		||||
      '.ant-formily-item .ant-formily-item-control .ant-formily-item-control-content .ant-formily-item-control-content-component':
 | 
			
		||||
        {
 | 
			
		||||
          lineHeight: 'normal',
 | 
			
		||||
@ -82,6 +90,10 @@ export const useStyles = genStyleHook('nb-variable', (token) => {
 | 
			
		||||
      '.ant-btn': {
 | 
			
		||||
        height: 'auto',
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      '.variable': {
 | 
			
		||||
        flex: 1,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,7 @@
 | 
			
		||||
/* globals define,module */
 | 
			
		||||
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
Using a Universal Module Loader that should be browser, require, and AMD friendly
 | 
			
		||||
http://ricostacruz.com/cheatsheets/umdjs.html
 | 
			
		||||
@ -146,50 +149,58 @@ export function getJsonLogic() {
 | 
			
		||||
      if (!a || !b) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      const milliseconds1 = new Date(a).getTime();
 | 
			
		||||
      const milliseconds2 = new Date(b).getTime();
 | 
			
		||||
      return milliseconds1 === milliseconds2;
 | 
			
		||||
      if (!Array.isArray(a)) {
 | 
			
		||||
        a = [a, a];
 | 
			
		||||
      }
 | 
			
		||||
      if (!Array.isArray(b)) {
 | 
			
		||||
        b = [b, b];
 | 
			
		||||
      }
 | 
			
		||||
      a = a.map((date) => dayjs(date));
 | 
			
		||||
      b = b.map((date) => dayjs(date));
 | 
			
		||||
 | 
			
		||||
      return a[0].isBetween(b[0], b[1], null, '[]') && a[1].isBetween(b[0], b[1], null, '[]');
 | 
			
		||||
    },
 | 
			
		||||
    $dateBefore: function (a, b) {
 | 
			
		||||
      if (!a || !b) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      if (!Array.isArray(a)) {
 | 
			
		||||
        a = [a, a];
 | 
			
		||||
      }
 | 
			
		||||
      if (!Array.isArray(b)) {
 | 
			
		||||
        b = [b, b];
 | 
			
		||||
      }
 | 
			
		||||
      a = a.map((date) => dayjs(date));
 | 
			
		||||
      b = b.map((date) => dayjs(date));
 | 
			
		||||
 | 
			
		||||
      return new Date(a) < new Date(b);
 | 
			
		||||
      return a[0].isBefore(b[0]) && a[1].isBefore(b[0]);
 | 
			
		||||
    },
 | 
			
		||||
    $dateNotBefore: function (a, b) {
 | 
			
		||||
      if (!a || !b) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return new Date(a) <= new Date(b);
 | 
			
		||||
      return !operations.$dateBefore(a, b);
 | 
			
		||||
    },
 | 
			
		||||
    $dateAfter: function (a, b) {
 | 
			
		||||
      if (!a || !b) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      if (!Array.isArray(a)) {
 | 
			
		||||
        a = [a, a];
 | 
			
		||||
      }
 | 
			
		||||
      if (!Array.isArray(b)) {
 | 
			
		||||
        b = [b, b];
 | 
			
		||||
      }
 | 
			
		||||
      a = a.map((date) => dayjs(date));
 | 
			
		||||
      b = b.map((date) => dayjs(date));
 | 
			
		||||
 | 
			
		||||
      return new Date(a) > new Date(b);
 | 
			
		||||
      return a[0].isAfter(b[1]) && a[1].isAfter(b[1]);
 | 
			
		||||
    },
 | 
			
		||||
    $dateNotAfter: function (a, b) {
 | 
			
		||||
      if (!a || !b) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      return new Date(a) >= new Date(b);
 | 
			
		||||
      return !operations.$dateAfter(a, b);
 | 
			
		||||
    },
 | 
			
		||||
    $dateBetween: function (a, b) {
 | 
			
		||||
      if (!a || !b) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      const startTime = new Date(b?.[0]);
 | 
			
		||||
      const endTime = new Date(b?.[1]);
 | 
			
		||||
      return new Date(a) > startTime && new Date(a) < endTime;
 | 
			
		||||
      return operations.$dateOn(a, b);
 | 
			
		||||
    },
 | 
			
		||||
    $dateNotOn: function (a, b) {
 | 
			
		||||
      if (!a || !b) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      return new Date(a) != new Date(b);
 | 
			
		||||
      return !operations.$dateOn(a, b);
 | 
			
		||||
    },
 | 
			
		||||
    $isTruly: function (a) {
 | 
			
		||||
      if (Array.isArray(a)) return a.some((k) => k === true || k === 1);
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,7 @@
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import flat from 'flat';
 | 
			
		||||
import _, { every, findIndex, isArray, some } from 'lodash';
 | 
			
		||||
import { useMemo } from 'react';
 | 
			
		||||
import { useTableBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCurrentUserContext } from '../../../user';
 | 
			
		||||
import _, { every, findIndex, some } from 'lodash';
 | 
			
		||||
import { VariableOption, VariablesContextType } from '../../../variables/types';
 | 
			
		||||
import { isVariable } from '../../../variables/utils/isVariable';
 | 
			
		||||
import { transformVariableValue } from '../../../variables/utils/transformVariableValue';
 | 
			
		||||
import { getJsonLogic } from '../../common/utils/logic';
 | 
			
		||||
 | 
			
		||||
type VariablesCtx = {
 | 
			
		||||
@ -13,42 +11,6 @@ type VariablesCtx = {
 | 
			
		||||
  $form?: Record<string, any>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function flattenDeep(data, result = []) {
 | 
			
		||||
  for (let i = 0; i < data?.length; i++) {
 | 
			
		||||
    const { children, ...rest } = data[i];
 | 
			
		||||
    result.push(rest);
 | 
			
		||||
    if (children) {
 | 
			
		||||
      flattenDeep(children, result);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useVariablesCtx = (): VariablesCtx => {
 | 
			
		||||
  const currentUser = useCurrentUserContext();
 | 
			
		||||
  const { field, service, rowKey } = useTableBlockContext();
 | 
			
		||||
  const tableData = flattenDeep(service?.data?.data);
 | 
			
		||||
  const contextData = tableData?.filter((v) => (field?.data?.selectedRowKeys || [])?.includes(v[rowKey]));
 | 
			
		||||
  return useMemo(() => {
 | 
			
		||||
    return {
 | 
			
		||||
      $user: currentUser?.data?.data || {},
 | 
			
		||||
      $date: {
 | 
			
		||||
        now: () => dayjs().toISOString(),
 | 
			
		||||
      },
 | 
			
		||||
      $context: contextData,
 | 
			
		||||
    };
 | 
			
		||||
  }, [contextData, currentUser?.data?.data]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const isVariable = (str: unknown) => {
 | 
			
		||||
  if (typeof str !== 'string') {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  const regex = /{{(.*?)}}/;
 | 
			
		||||
  const matches = str?.match?.(regex);
 | 
			
		||||
  return matches ? true : false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const parseVariables = (str: string, ctx: VariablesCtx | any) => {
 | 
			
		||||
  const regex = /{{(.*?)}}/;
 | 
			
		||||
  const matches = str?.match?.(regex);
 | 
			
		||||
@ -76,41 +38,6 @@ export function getInnermostKeyAndValue(obj) {
 | 
			
		||||
  return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getFieldValue = (fieldPath, values) => {
 | 
			
		||||
  const v = fieldPath[0];
 | 
			
		||||
  const h = fieldPath[1];
 | 
			
		||||
  const regex = new RegExp('^' + v + '\\..+\\.' + h + '$'); // 构建匹配的正则表达式
 | 
			
		||||
  const matchedValues = [];
 | 
			
		||||
  const data = flat.flatten(values, { maxDepth: 3 });
 | 
			
		||||
  for (const key in data) {
 | 
			
		||||
    if (regex.test(key)) {
 | 
			
		||||
      matchedValues.push(data[key]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return matchedValues;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getValue = (str: string, values) => {
 | 
			
		||||
  const regex = /{{(.*?)}}/;
 | 
			
		||||
  const matches = str?.match?.(regex);
 | 
			
		||||
  if (matches) {
 | 
			
		||||
    return getVariableValue(str, values);
 | 
			
		||||
  } else {
 | 
			
		||||
    return str;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
const getVariableValue = (str, values) => {
 | 
			
		||||
  const regex = /{{[^.]+\.([^}]+)}}/;
 | 
			
		||||
  const match = regex.exec(str);
 | 
			
		||||
  const targetField = match?.[1]?.split('.') || [];
 | 
			
		||||
  const isArrayField = isArray(values[targetField[0]]);
 | 
			
		||||
  if (isArrayField && targetField.length > 1) {
 | 
			
		||||
    //对多关系字段
 | 
			
		||||
    return getFieldValue(targetField, values);
 | 
			
		||||
  } else {
 | 
			
		||||
    return flat(values)[match?.[1]];
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
const getTargetField = (obj) => {
 | 
			
		||||
  const keys = getAllKeys(obj);
 | 
			
		||||
  const index = findIndex(keys, (key, index, keys) => {
 | 
			
		||||
@ -138,39 +65,71 @@ function getAllKeys(obj) {
 | 
			
		||||
  return keys;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const conditionAnalyse = (rules, scope) => {
 | 
			
		||||
  const values = { ...scope, now: new Date() };
 | 
			
		||||
export const conditionAnalyses = async ({
 | 
			
		||||
  rules,
 | 
			
		||||
  formValues,
 | 
			
		||||
  variables,
 | 
			
		||||
  localVariables,
 | 
			
		||||
}: {
 | 
			
		||||
  rules;
 | 
			
		||||
  formValues;
 | 
			
		||||
  variables: VariablesContextType;
 | 
			
		||||
  localVariables: VariableOption[];
 | 
			
		||||
}) => {
 | 
			
		||||
  if (process.env.NODE_ENV !== 'production') {
 | 
			
		||||
    if (!variables) {
 | 
			
		||||
      throw new Error(`conditionAnalyses: variables cannot be ${variables}`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const type = Object.keys(rules)[0] || '$and';
 | 
			
		||||
  const conditions = rules[type];
 | 
			
		||||
  const results = conditions.map((c) => {
 | 
			
		||||
 | 
			
		||||
  let results = conditions.map(async (c) => {
 | 
			
		||||
    const jsonlogic = getInnermostKeyAndValue(c);
 | 
			
		||||
    const operator = jsonlogic?.key;
 | 
			
		||||
    const value = getValue(jsonlogic?.value, values);
 | 
			
		||||
    const targetField = getTargetField(c);
 | 
			
		||||
 | 
			
		||||
    if (!operator) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      const isArrayField = isArray(values[targetField[0]]);
 | 
			
		||||
      const jsonLogic = getJsonLogic();
 | 
			
		||||
 | 
			
		||||
      if (isArrayField && targetField.length > 1) {
 | 
			
		||||
        //对多关系字段比较
 | 
			
		||||
        const currentValue = getFieldValue(targetField, values);
 | 
			
		||||
        const result = jsonLogic.apply({ [operator]: [currentValue, value] });
 | 
			
		||||
        return result;
 | 
			
		||||
      } else {
 | 
			
		||||
        const currentValue = targetField.length > 1 ? flat(values)?.[targetField.join('.')] : values?.[targetField[0]];
 | 
			
		||||
        const result = jsonLogic.apply({ [operator]: [currentValue, value] });
 | 
			
		||||
        return result;
 | 
			
		||||
      }
 | 
			
		||||
    const targetVariableName = targetFieldToVariableString(getTargetField(c));
 | 
			
		||||
    const parsingResult = isVariable(jsonlogic?.value)
 | 
			
		||||
      ? [
 | 
			
		||||
          variables.parseVariable(jsonlogic?.value, localVariables),
 | 
			
		||||
          variables.parseVariable(targetVariableName, localVariables),
 | 
			
		||||
        ]
 | 
			
		||||
      : [jsonlogic?.value, variables.parseVariable(targetVariableName, localVariables)];
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const jsonLogic = getJsonLogic();
 | 
			
		||||
      const [value, targetValue] = await Promise.all(parsingResult);
 | 
			
		||||
      const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables);
 | 
			
		||||
      return jsonLogic.apply({
 | 
			
		||||
        [operator]: [
 | 
			
		||||
          transformVariableValue(targetValue, { targetCollectionField }),
 | 
			
		||||
          transformVariableValue(value, { targetCollectionField }),
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(error);
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  results = await Promise.all(results);
 | 
			
		||||
 | 
			
		||||
  if (type === '$and') {
 | 
			
		||||
    return every(results, (v) => v);
 | 
			
		||||
  } else {
 | 
			
		||||
    return some(results, (v) => v);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 转化成变量字符串,方便解析出值
 | 
			
		||||
 * @param targetField
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
function targetFieldToVariableString(targetField: string[]) {
 | 
			
		||||
  // Action 中的联动规则虽然没有 form 上下文但是在这里也使用的是 `$nForm` 变量,这样实现更简单
 | 
			
		||||
  return `{{ $nForm.${targetField.join('.')} }}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,16 @@
 | 
			
		||||
import { Schema, SchemaExpressionScopeContext, SchemaOptionsContext } from '@formily/react';
 | 
			
		||||
import { useContext, isValidElement } from 'react';
 | 
			
		||||
import { isValidElement, useContext } from 'react';
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  /**
 | 
			
		||||
   * 不使用缓存
 | 
			
		||||
   */
 | 
			
		||||
  noCache?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const compileCache = {};
 | 
			
		||||
 | 
			
		||||
export const useCompile = () => {
 | 
			
		||||
export const useCompile = ({ noCache }: Props = { noCache: false }) => {
 | 
			
		||||
  const options = useContext(SchemaOptionsContext);
 | 
			
		||||
  const scope = useContext(SchemaExpressionScopeContext);
 | 
			
		||||
  return (source: any, ext?: any) => {
 | 
			
		||||
@ -33,6 +40,9 @@ export const useCompile = () => {
 | 
			
		||||
        return Schema.compile(source, mergedScope);
 | 
			
		||||
      }
 | 
			
		||||
      try {
 | 
			
		||||
        if (noCache) {
 | 
			
		||||
          return Schema.compile(source, mergedScope);
 | 
			
		||||
        }
 | 
			
		||||
        compileCache[cacheKey] = compileCache[cacheKey] || Schema.compile(source, mergedScope);
 | 
			
		||||
        return compileCache[cacheKey];
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ export const TableColumnInitializers = (props: any) => {
 | 
			
		||||
          {
 | 
			
		||||
            type: 'itemGroup',
 | 
			
		||||
            title: t(`Parent collection fields`) + '(' + compile(`${Object.keys(inherit)[0]}`) + ')',
 | 
			
		||||
            children: Object.values(inherit)[0].filter((v) => !v?.field?.isForeignKey),
 | 
			
		||||
            children: Object.values(inherit)[0].filter((v: any) => !v?.field?.isForeignKey),
 | 
			
		||||
          },
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import { useRecord } from '../../record-provider';
 | 
			
		||||
import { ActionContextProvider, useActionContext, useCompile } from '../../schema-component';
 | 
			
		||||
import { linkageAction } from '../../schema-component/antd/action/utils';
 | 
			
		||||
import { parseVariables } from '../../schema-component/common/utils/uitls';
 | 
			
		||||
import { useLocalVariables, useVariables } from '../../variables';
 | 
			
		||||
 | 
			
		||||
export const actionDesignerCss = css`
 | 
			
		||||
  position: relative;
 | 
			
		||||
@ -89,19 +90,28 @@ export const CreateRecordAction = observer(
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const field: any = useField();
 | 
			
		||||
    const [currentCollection, setCurrentCollection] = useState(collection.name);
 | 
			
		||||
    const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
 | 
			
		||||
    const linkageRules: any[] = fieldSchema?.['x-linkage-rules'] || [];
 | 
			
		||||
    const values = useRecord();
 | 
			
		||||
    const ctx = useActionContext();
 | 
			
		||||
    const variables = useVariables();
 | 
			
		||||
    const localVariables = useLocalVariables({ currentForm: { values } as any });
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      field.linkageProperty = {};
 | 
			
		||||
      linkageRules
 | 
			
		||||
        .filter((k) => !k.disabled)
 | 
			
		||||
        .map((v) => {
 | 
			
		||||
          return v.actions?.map((h) => {
 | 
			
		||||
            linkageAction(h.operator, field, v.condition, values);
 | 
			
		||||
        .forEach((v) => {
 | 
			
		||||
          v.actions?.forEach((h) => {
 | 
			
		||||
            linkageAction({
 | 
			
		||||
              operator: h.operator,
 | 
			
		||||
              field,
 | 
			
		||||
              condition: v.condition,
 | 
			
		||||
              values,
 | 
			
		||||
              variables,
 | 
			
		||||
              localVariables,
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
    }, [linkageRules, values]);
 | 
			
		||||
        });
 | 
			
		||||
    }, [JSON.stringify(linkageRules), values]);
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={actionDesignerCss}>
 | 
			
		||||
        <ActionContextProvider value={{ ...ctx, visible, setVisible }}>
 | 
			
		||||
@ -142,6 +152,8 @@ export const CreateAction = observer(
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const field: any = useField();
 | 
			
		||||
    const form = useForm();
 | 
			
		||||
    const variables = useVariables();
 | 
			
		||||
 | 
			
		||||
    const enableChildren = fieldSchema['x-enable-children'] || [];
 | 
			
		||||
    const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
 | 
			
		||||
    const linkageFromForm = fieldSchema?.['x-component-props']?.['linkageFromForm'];
 | 
			
		||||
@ -168,8 +180,9 @@ export const CreateAction = observer(
 | 
			
		||||
          return v && actionAclCheck(`${v.name}:create`);
 | 
			
		||||
        });
 | 
			
		||||
    }, [enableChildren, totalChildCollections]);
 | 
			
		||||
    const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
 | 
			
		||||
    const linkageRules: any[] = fieldSchema?.['x-linkage-rules'] || [];
 | 
			
		||||
    const values = useRecord();
 | 
			
		||||
    const localVariables = useLocalVariables({ currentForm: { values } as any });
 | 
			
		||||
    const compile = useCompile();
 | 
			
		||||
    const { designable } = useDesignable();
 | 
			
		||||
    const icon = props.icon || null;
 | 
			
		||||
@ -188,15 +201,21 @@ export const CreateAction = observer(
 | 
			
		||||
    }, [menuItems]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      field.linkageProperty = {};
 | 
			
		||||
      linkageRules
 | 
			
		||||
        .filter((k) => !k.disabled)
 | 
			
		||||
        .map((v) => {
 | 
			
		||||
          return v.actions?.map((h) => {
 | 
			
		||||
            linkageAction(h.operator, field, v.condition, values);
 | 
			
		||||
        .forEach((v) => {
 | 
			
		||||
          v.actions?.forEach((h) => {
 | 
			
		||||
            linkageAction({
 | 
			
		||||
              operator: h.operator,
 | 
			
		||||
              field,
 | 
			
		||||
              condition: v.condition,
 | 
			
		||||
              values,
 | 
			
		||||
              variables,
 | 
			
		||||
              localVariables,
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
    }, [linkageRules, values]);
 | 
			
		||||
        });
 | 
			
		||||
    }, [JSON.stringify(linkageRules), values]);
 | 
			
		||||
    return (
 | 
			
		||||
      <div className={actionDesignerCss}>
 | 
			
		||||
        {FinallyButton({
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,30 @@
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { Field } from '@formily/core';
 | 
			
		||||
import { connect, useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { useField, useFieldSchema } from '@formily/react';
 | 
			
		||||
import { merge } from '@formily/shared';
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import React, { useCallback, useEffect, useMemo } from 'react';
 | 
			
		||||
import { useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import {
 | 
			
		||||
  CollectionFieldProvider,
 | 
			
		||||
  useCollection,
 | 
			
		||||
  useCollectionField,
 | 
			
		||||
  useCollectionFilterOptions,
 | 
			
		||||
  useCollectionManager,
 | 
			
		||||
} from '../../../collection-manager';
 | 
			
		||||
import { Variable, useCompile, useComponent, useVariableScope } from '../../../schema-component';
 | 
			
		||||
import { useRecord } from '../../../record-provider';
 | 
			
		||||
import { useCompile, useComponent } from '../../../schema-component';
 | 
			
		||||
import { VariableInput, getShouldChange } from '../../../schema-settings/VariableInput/VariableInput';
 | 
			
		||||
import { Option } from '../../../schema-settings/VariableInput/type';
 | 
			
		||||
import { formatVariableScop } from '../../../schema-settings/VariableInput/utils/formatVariableScop';
 | 
			
		||||
import { useLocalVariables, useVariables } from '../../../variables';
 | 
			
		||||
import { DeletedField } from '../DeletedField';
 | 
			
		||||
 | 
			
		||||
interface AssignedFieldProps {
 | 
			
		||||
  value: any;
 | 
			
		||||
  onChange: (value: any) => void;
 | 
			
		||||
  [key: string]: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const InternalField: React.FC = (props) => {
 | 
			
		||||
  const field = useField<Field>();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
@ -68,72 +79,73 @@ const InternalField: React.FC = (props) => {
 | 
			
		||||
  return React.createElement(component, props, props.children);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const CollectionField = connect((props) => {
 | 
			
		||||
const CollectionField = (props) => {
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  return (
 | 
			
		||||
    <CollectionFieldProvider name={fieldSchema.name} fallback={<DeletedField />}>
 | 
			
		||||
      <InternalField {...props} />
 | 
			
		||||
    </CollectionFieldProvider>
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export enum AssignedFieldValueType {
 | 
			
		||||
  ConstantValue = 'constantValue',
 | 
			
		||||
  DynamicValue = 'dynamicValue',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AssignedField = (props: any) => {
 | 
			
		||||
export const AssignedField = (props: AssignedFieldProps) => {
 | 
			
		||||
  const { value, onChange } = props;
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const compile = useCompile();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const { getField } = useCollection();
 | 
			
		||||
  const collectionField = getField(fieldSchema.name);
 | 
			
		||||
  const [options, setOptions] = useState<any[]>([]);
 | 
			
		||||
  const { getCollectionFields, getAllCollectionsInheritChain } = useCollectionManager();
 | 
			
		||||
  const collection = useCollection();
 | 
			
		||||
  const fields = compile(useCollectionFilterOptions(collection));
 | 
			
		||||
  const userFields = compile(useCollectionFilterOptions('users'));
 | 
			
		||||
  const scope = useVariableScope();
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const opt = [
 | 
			
		||||
      {
 | 
			
		||||
        name: 'currentRecord',
 | 
			
		||||
        title: t('Current record'),
 | 
			
		||||
        children: fields,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'currentUser',
 | 
			
		||||
        title: t('Current user'),
 | 
			
		||||
        children: userFields,
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
    if (['createdAt', 'datetime', 'time', 'updatedAt'].includes(collectionField?.interface)) {
 | 
			
		||||
      opt.unshift({
 | 
			
		||||
        name: 'currentTime',
 | 
			
		||||
        title: t('Current time'),
 | 
			
		||||
        children: null,
 | 
			
		||||
      });
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const fieldSchema = useFieldSchema();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const variables = useVariables();
 | 
			
		||||
  const localVariables = useLocalVariables();
 | 
			
		||||
  const currentFormFields = useCollectionFilterOptions(collection);
 | 
			
		||||
 | 
			
		||||
  const { name, getField } = collection;
 | 
			
		||||
  const collectionField = getField(fieldSchema.name);
 | 
			
		||||
 | 
			
		||||
  const shouldChange = useMemo(
 | 
			
		||||
    () => getShouldChange({ collectionField, variables, localVariables, getAllCollectionsInheritChain }),
 | 
			
		||||
    [collectionField, getAllCollectionsInheritChain, localVariables, variables],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const returnScope = useCallback(
 | 
			
		||||
    (scope: Option[]) => {
 | 
			
		||||
      const currentForm = scope.find((item) => item.value === '$nForm');
 | 
			
		||||
      const fields = getCollectionFields(name);
 | 
			
		||||
 | 
			
		||||
      // fix https://nocobase.height.app/T-1355
 | 
			
		||||
      // 工作流人工节点的 `自定义表单` 区块,与其它表单区块不同,根据它的数据表名称,获取到的字段列表为空,所以需要在这里特殊处理一下
 | 
			
		||||
      if (!fields?.length && currentForm) {
 | 
			
		||||
        currentForm.children = formatVariableScop(currentFormFields);
 | 
			
		||||
      }
 | 
			
		||||
    const next = opt.concat(scope);
 | 
			
		||||
    setOptions(next);
 | 
			
		||||
  }, [fields, userFields, scope]);
 | 
			
		||||
 | 
			
		||||
      return scope;
 | 
			
		||||
    },
 | 
			
		||||
    [currentFormFields, name],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const renderSchemaComponent = useCallback(
 | 
			
		||||
    ({ value, onChange }): React.JSX.Element => {
 | 
			
		||||
      return <CollectionField {...props} value={value} onChange={onChange} />;
 | 
			
		||||
    },
 | 
			
		||||
    [JSON.stringify(_.omit(props, 'value'))],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Variable.Input
 | 
			
		||||
    <VariableInput
 | 
			
		||||
      form={form}
 | 
			
		||||
      record={record}
 | 
			
		||||
      value={value}
 | 
			
		||||
      onChange={onChange}
 | 
			
		||||
      scope={options}
 | 
			
		||||
      className={css`
 | 
			
		||||
        .variable {
 | 
			
		||||
          width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
      `}
 | 
			
		||||
      fieldNames={{
 | 
			
		||||
        label: 'title',
 | 
			
		||||
        value: 'name',
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <CollectionField value={value} onChange={onChange} />
 | 
			
		||||
    </Variable.Input>
 | 
			
		||||
      renderSchemaComponent={renderSchemaComponent}
 | 
			
		||||
      collectionField={collectionField}
 | 
			
		||||
      shouldChange={shouldChange}
 | 
			
		||||
      returnScope={returnScope}
 | 
			
		||||
      targetFieldSchema={fieldSchema}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
import { Field, Form } from '@formily/core';
 | 
			
		||||
import { ISchema, Schema, useFieldSchema, useForm } from '@formily/react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { useContext, useMemo } from 'react';
 | 
			
		||||
import { useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { BlockRequestContext, SchemaInitializerItemOptions } from '..';
 | 
			
		||||
import { SchemaInitializerItemOptions, useFormActiveFields, useFormBlockContext } from '../';
 | 
			
		||||
import { CollectionFieldOptions, FieldOptions, useCollection, useCollectionManager } from '../collection-manager';
 | 
			
		||||
import { isAssocField } from '../filter-provider/utils';
 | 
			
		||||
import { useActionContext, useDesignable } from '../schema-component';
 | 
			
		||||
@ -744,7 +745,10 @@ const recursiveParent = (schema: Schema) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useCurrentSchema = (action: string, key: string, find = findSchema, rm = removeSchema) => {
 | 
			
		||||
  const { removeActiveFieldName } = useFormActiveFields() || {};
 | 
			
		||||
  const { form }: { form: Form } = useFormBlockContext();
 | 
			
		||||
  let fieldSchema = useFieldSchema();
 | 
			
		||||
 | 
			
		||||
  if (!fieldSchema?.['x-initializer']) {
 | 
			
		||||
    const recursiveInitializerSchema = recursiveParent(fieldSchema);
 | 
			
		||||
    if (recursiveInitializerSchema) {
 | 
			
		||||
@ -753,16 +757,15 @@ export const useCurrentSchema = (action: string, key: string, find = findSchema,
 | 
			
		||||
  }
 | 
			
		||||
  const { remove } = useDesignable();
 | 
			
		||||
  const schema = find(fieldSchema, key, action);
 | 
			
		||||
  const ctx = useContext(BlockRequestContext);
 | 
			
		||||
  return {
 | 
			
		||||
    schema,
 | 
			
		||||
    exists: !!schema,
 | 
			
		||||
    remove() {
 | 
			
		||||
      if (ctx.field) {
 | 
			
		||||
        ctx.field.data = ctx.field.data || {};
 | 
			
		||||
        ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
 | 
			
		||||
        ctx.field.data.activeFields.delete(schema.name);
 | 
			
		||||
      }
 | 
			
		||||
      removeActiveFieldName?.(schema.name);
 | 
			
		||||
      form?.query(schema.name).forEach((field: Field) => {
 | 
			
		||||
        field.setInitialValue(null);
 | 
			
		||||
        field.reset();
 | 
			
		||||
      });
 | 
			
		||||
      schema && rm(schema, remove);
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -6,11 +6,10 @@ import _ from 'lodash';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { GeneralSchemaDesigner, SchemaSettings } from '../..';
 | 
			
		||||
import { mergeFilter } from '../../../block-provider';
 | 
			
		||||
import { useCollectionFilterOptions, useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { mergeFilter, useFormBlockContext } from '../../../block-provider';
 | 
			
		||||
import { useCollectionManager } from '../../../collection-manager';
 | 
			
		||||
import { removeNullCondition, useCompile, useDesignable } from '../../../schema-component';
 | 
			
		||||
import { ITemplate } from '../../../schema-component/antd/form-v2/Templates';
 | 
			
		||||
import { FilterDynamicComponent } from '../../../schema-component/antd/table-v2/FilterDynamicComponent';
 | 
			
		||||
 | 
			
		||||
export const Designer = observer(
 | 
			
		||||
  () => {
 | 
			
		||||
@ -24,13 +23,13 @@ export const Designer = observer(
 | 
			
		||||
      formSchema: ISchema;
 | 
			
		||||
      data?: ITemplate;
 | 
			
		||||
    };
 | 
			
		||||
    const { form } = useFormBlockContext();
 | 
			
		||||
 | 
			
		||||
    // 在这里读取 resource 的值,当 resource 变化时,会触发该组件的更新
 | 
			
		||||
    const collectionName = field.componentProps.service.resource;
 | 
			
		||||
 | 
			
		||||
    const collection = getCollection(collectionName);
 | 
			
		||||
    const collectionFields = getCollectionFields(collectionName);
 | 
			
		||||
    const dataSource = useCollectionFilterOptions(collectionName);
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      error('data is required');
 | 
			
		||||
@ -63,25 +62,10 @@ export const Designer = observer(
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <GeneralSchemaDesigner draggable={false}>
 | 
			
		||||
        <SchemaSettings.ModalItem
 | 
			
		||||
          title={t('Set the data scope')}
 | 
			
		||||
          schema={
 | 
			
		||||
            {
 | 
			
		||||
              type: 'object',
 | 
			
		||||
              title: t('Set the data scope'),
 | 
			
		||||
              properties: {
 | 
			
		||||
                filter: {
 | 
			
		||||
                  default: getFilter(),
 | 
			
		||||
                  // title: '数据范围',
 | 
			
		||||
                  enum: dataSource,
 | 
			
		||||
                  'x-component': 'Filter',
 | 
			
		||||
                  'x-component-props': {
 | 
			
		||||
                    dynamicComponent: (props) => FilterDynamicComponent({ ...props }),
 | 
			
		||||
                  },
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            } as ISchema
 | 
			
		||||
          }
 | 
			
		||||
        <SchemaSettings.DataScope
 | 
			
		||||
          collectionName={collectionName}
 | 
			
		||||
          defaultFilter={getFilter()}
 | 
			
		||||
          form={form}
 | 
			
		||||
          onSubmit={({ filter }) => {
 | 
			
		||||
            setFilter(filter);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,21 +7,18 @@ import { useFormVariable } from '../VariableInput/hooks/useFormVariable';
 | 
			
		||||
import { useIterationVariable } from '../VariableInput/hooks/useIterationVariable';
 | 
			
		||||
 | 
			
		||||
export const ChildDynamicComponent = observer(
 | 
			
		||||
  (props: { rootCollection: string; form: any; onChange; value; default; collectionField }) => {
 | 
			
		||||
    const { form, rootCollection, onChange, value, collectionField } = props;
 | 
			
		||||
  (props: { rootCollection: string; onChange; value; default; collectionField }) => {
 | 
			
		||||
    const { rootCollection, onChange, value, collectionField } = props;
 | 
			
		||||
    const fieldSchema = useFieldSchema();
 | 
			
		||||
    const formVariabele = useFormVariable({ blockForm: form, rootCollection });
 | 
			
		||||
    const iterationVariabele = useIterationVariable({
 | 
			
		||||
      blockForm: form,
 | 
			
		||||
      currentCollection: collectionField.collectionName,
 | 
			
		||||
      rootCollection,
 | 
			
		||||
    const formVariable = useFormVariable({ collectionName: rootCollection, collectionField });
 | 
			
		||||
    const iterationVariable = useIterationVariable({
 | 
			
		||||
      currentCollection: collectionField?.collectionName,
 | 
			
		||||
      schema: collectionField?.uiSchema,
 | 
			
		||||
      collectionField,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const compile = useCompile();
 | 
			
		||||
    const result = useMemo(
 | 
			
		||||
      () => [formVariabele, iterationVariabele].filter(Boolean),
 | 
			
		||||
      [formVariabele, iterationVariabele],
 | 
			
		||||
    );
 | 
			
		||||
    const result = useMemo(() => [formVariable, iterationVariable].filter(Boolean), [formVariable, iterationVariable]);
 | 
			
		||||
    const scope = compile(result);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      onChange(fieldSchema.default);
 | 
			
		||||
 | 
			
		||||
@ -1,20 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import { Variable } from '../../schema-component';
 | 
			
		||||
import { useVariableOptions } from './Variables';
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  value: any;
 | 
			
		||||
  onChange: (value: any) => void;
 | 
			
		||||
  renderSchemaComponent: () => React.ReactNode;
 | 
			
		||||
  collectionName: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function FilterDynamicComponent(props: Props) {
 | 
			
		||||
  const { value, onChange, renderSchemaComponent, collectionName } = props;
 | 
			
		||||
  const scope = useVariableOptions(collectionName, ['o2m', 'm2m']);
 | 
			
		||||
  return (
 | 
			
		||||
    <Variable.Input value={value} onChange={onChange} scope={scope}>
 | 
			
		||||
      {renderSchemaComponent()}
 | 
			
		||||
    </Variable.Input>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -4,7 +4,7 @@ import { TreeSelect } from '@formily/antd-v5';
 | 
			
		||||
import { observer } from '@formily/react';
 | 
			
		||||
import { uid } from '@formily/shared';
 | 
			
		||||
import { Select, Space } from 'antd';
 | 
			
		||||
import React, { useContext, useState } from 'react';
 | 
			
		||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useCompile } from '../..';
 | 
			
		||||
import { DynamicComponent } from './DynamicComponent';
 | 
			
		||||
@ -110,40 +110,57 @@ export const FormButtonLinkageRuleAction = observer(
 | 
			
		||||
    const { value, options } = props;
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const compile = useCompile();
 | 
			
		||||
    const [editFalg, setEditFlag] = useState(false);
 | 
			
		||||
    const [editFlag, setEditFlag] = useState(false);
 | 
			
		||||
    const remove = useContext(RemoveActionContext);
 | 
			
		||||
    const { schema, operator, setOperator, setValue } = useValues(options);
 | 
			
		||||
    const operators = [
 | 
			
		||||
    const operators = useMemo(
 | 
			
		||||
      () =>
 | 
			
		||||
        compile([
 | 
			
		||||
          { label: t('Visible'), value: ActionType.Visible, schema: {} },
 | 
			
		||||
          { label: t('Hidden'), value: ActionType.Hidden, schema: {} },
 | 
			
		||||
          { label: t('Disabled'), value: ActionType.Disabled, schema: {} },
 | 
			
		||||
          { label: t('Enabled'), value: ActionType.Active, schema: {} },
 | 
			
		||||
    ];
 | 
			
		||||
        ]),
 | 
			
		||||
      [compile, t],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const onChange = useCallback(
 | 
			
		||||
      (value) => {
 | 
			
		||||
        const flag = [ActionType.Value].includes(value);
 | 
			
		||||
        setEditFlag(flag);
 | 
			
		||||
        setOperator(value);
 | 
			
		||||
      },
 | 
			
		||||
      [setOperator],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const onChangeValue = useCallback(
 | 
			
		||||
      (value) => {
 | 
			
		||||
        setValue(value);
 | 
			
		||||
      },
 | 
			
		||||
      [setValue],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const closeStyle = useMemo(() => ({ color: '#bfbfbf' }), []);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div style={{ marginBottom: 8 }}>
 | 
			
		||||
        <Space>
 | 
			
		||||
          <Select
 | 
			
		||||
            popupMatchSelectWidth={false}
 | 
			
		||||
            value={operator}
 | 
			
		||||
            options={compile(operators)}
 | 
			
		||||
            onChange={(value) => {
 | 
			
		||||
              const flag = [ActionType.Value].includes(value);
 | 
			
		||||
              setEditFlag(flag);
 | 
			
		||||
              setOperator(value);
 | 
			
		||||
            }}
 | 
			
		||||
            options={operators}
 | 
			
		||||
            onChange={onChange}
 | 
			
		||||
            placeholder={t('action')}
 | 
			
		||||
          />
 | 
			
		||||
          {editFalg &&
 | 
			
		||||
          {editFlag &&
 | 
			
		||||
            React.createElement(DynamicComponent, {
 | 
			
		||||
              value,
 | 
			
		||||
              schema,
 | 
			
		||||
              onChange(value) {
 | 
			
		||||
                setValue(value);
 | 
			
		||||
              },
 | 
			
		||||
              onChange: onChangeValue,
 | 
			
		||||
            })}
 | 
			
		||||
          {!props.disabled && (
 | 
			
		||||
            <a data-testid="close-icon-button">
 | 
			
		||||
              <CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
 | 
			
		||||
              <CloseCircleOutlined onClick={remove} style={closeStyle} />
 | 
			
		||||
            </a>
 | 
			
		||||
          )}
 | 
			
		||||
        </Space>
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import { ArrayField as ArrayFieldModel, VoidField } from '@formily/core';
 | 
			
		||||
import { ArrayField, ObjectField, observer, useField } from '@formily/react';
 | 
			
		||||
import { Space } from 'antd';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import React, { useCallback, useMemo } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { RemoveActionContext } from './context';
 | 
			
		||||
import { FormButtonLinkageRuleAction, FormFieldLinkageRuleAction } from './LinkageRuleAction';
 | 
			
		||||
import { RemoveActionContext } from './context';
 | 
			
		||||
export const LinkageRuleActions = observer(
 | 
			
		||||
  (props: any): any => {
 | 
			
		||||
    const { type, linkageOptions } = props;
 | 
			
		||||
@ -26,29 +26,38 @@ export const LinkageRuleActions = observer(
 | 
			
		||||
  { displayName: 'LinkageRuleActions' },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const LinkageRuleActionGroup = (props) => {
 | 
			
		||||
interface LinkageRuleActionGroupProps {
 | 
			
		||||
  useProps: () => {
 | 
			
		||||
    type: 'button' | 'field';
 | 
			
		||||
    linkageOptions: any;
 | 
			
		||||
    collectionName: string;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const LinkageRuleActionGroup = (props: LinkageRuleActionGroupProps) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const field = useField<VoidField>();
 | 
			
		||||
  const logic = 'actions';
 | 
			
		||||
  const { type, linkageOptions, collectionName } = props?.useProps();
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={{ marginLeft: 10 }}>
 | 
			
		||||
      <ArrayField
 | 
			
		||||
        name={logic}
 | 
			
		||||
        component={[LinkageRuleActions, { type, linkageOptions, collectionName }]}
 | 
			
		||||
        disabled={false}
 | 
			
		||||
      />
 | 
			
		||||
      <Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
 | 
			
		||||
        <a
 | 
			
		||||
          onClick={() => {
 | 
			
		||||
  const { type, linkageOptions, collectionName } = props.useProps();
 | 
			
		||||
 | 
			
		||||
  const style = useMemo(() => ({ marginLeft: 10 }), []);
 | 
			
		||||
  const components = useMemo(
 | 
			
		||||
    () => [LinkageRuleActions, { type, linkageOptions, collectionName }],
 | 
			
		||||
    [collectionName, linkageOptions, type],
 | 
			
		||||
  );
 | 
			
		||||
  const spaceStyle = useMemo(() => ({ marginTop: 8, marginBottom: 8 }), []);
 | 
			
		||||
  const onClick = useCallback(() => {
 | 
			
		||||
    const f = field.query('.actions').take() as ArrayFieldModel;
 | 
			
		||||
    const items = f.value || [];
 | 
			
		||||
    items.push({});
 | 
			
		||||
    f.value = items;
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          {t('Add property')}
 | 
			
		||||
        </a>
 | 
			
		||||
  }, [field]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={style}>
 | 
			
		||||
      <ArrayField name={logic} component={components} disabled={false} />
 | 
			
		||||
      <Space size={16} style={spaceStyle}>
 | 
			
		||||
        <a onClick={onClick}>{t('Add property')}</a>
 | 
			
		||||
      </Space>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,107 @@
 | 
			
		||||
import { Input, Select } from 'antd';
 | 
			
		||||
import React, { useState } from 'react';
 | 
			
		||||
import React, { useCallback, useMemo, useState } from 'react';
 | 
			
		||||
import { useTranslation } from 'react-i18next';
 | 
			
		||||
import { useFormBlockContext } from '../../block-provider';
 | 
			
		||||
import { useRecord } from '../../record-provider';
 | 
			
		||||
import { Variable } from '.././../schema-component';
 | 
			
		||||
import { useCompatOldVariables } from '../VariableInput/VariableInput';
 | 
			
		||||
import { useVariableOptions } from '../VariableInput/hooks/useVariableOptions';
 | 
			
		||||
import { DynamicComponent } from './DynamicComponent';
 | 
			
		||||
import { useVariableOptions } from './Variables';
 | 
			
		||||
 | 
			
		||||
const { Option } = Select;
 | 
			
		||||
 | 
			
		||||
export const ValueDynamicComponent = (props) => {
 | 
			
		||||
interface ValueDynamicComponentProps {
 | 
			
		||||
  fieldValue: any;
 | 
			
		||||
  schema: any;
 | 
			
		||||
  setValue: (value: any) => void;
 | 
			
		||||
  collectionName: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const ValueDynamicComponent = (props: ValueDynamicComponentProps) => {
 | 
			
		||||
  const { fieldValue, schema, setValue, collectionName } = props;
 | 
			
		||||
  const [mode, setMode] = useState(fieldValue?.mode || 'constant');
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const scope = useVariableOptions(collectionName);
 | 
			
		||||
  const { form } = useFormBlockContext();
 | 
			
		||||
  const record = useRecord();
 | 
			
		||||
  const scope = useVariableOptions({
 | 
			
		||||
    collectionField: { uiSchema: schema },
 | 
			
		||||
    form,
 | 
			
		||||
    record,
 | 
			
		||||
    uiSchema: schema,
 | 
			
		||||
  });
 | 
			
		||||
  const { compatOldVariables } = useCompatOldVariables({
 | 
			
		||||
    collectionField: { uiSchema: schema, collectionName },
 | 
			
		||||
    uiSchema: schema,
 | 
			
		||||
    blockCollectionName: collectionName,
 | 
			
		||||
  });
 | 
			
		||||
  const constantStyle = useMemo(() => {
 | 
			
		||||
    return { minWidth: 150, maxWidth: 430, marginLeft: 5 };
 | 
			
		||||
  }, []);
 | 
			
		||||
  const handleChangeOfConstant = useCallback(
 | 
			
		||||
    (value) => {
 | 
			
		||||
      setValue({
 | 
			
		||||
        mode,
 | 
			
		||||
        value,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [mode, setValue],
 | 
			
		||||
  );
 | 
			
		||||
  const expressStyle = useMemo(() => {
 | 
			
		||||
    return { minWidth: 150, maxWidth: 430, fontSize: 13 };
 | 
			
		||||
  }, []);
 | 
			
		||||
  const handleChangeOfExpress = useCallback(
 | 
			
		||||
    (value) => {
 | 
			
		||||
      const result = value.replaceAll(`${collectionName}.`, '').replaceAll('$system.', '').trim();
 | 
			
		||||
      setValue({
 | 
			
		||||
        mode,
 | 
			
		||||
        value,
 | 
			
		||||
        result,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [collectionName, mode, setValue],
 | 
			
		||||
  );
 | 
			
		||||
  const textAreaStyle = useMemo(() => {
 | 
			
		||||
    return { minWidth: 460, marginRight: 15 };
 | 
			
		||||
  }, []);
 | 
			
		||||
  const compatScope = useMemo(() => {
 | 
			
		||||
    return compatOldVariables(scope, {
 | 
			
		||||
      value: fieldValue?.value,
 | 
			
		||||
    });
 | 
			
		||||
  }, [compatOldVariables, fieldValue?.value, scope]);
 | 
			
		||||
 | 
			
		||||
  const modeMap = {
 | 
			
		||||
    // 常量
 | 
			
		||||
    constant: (
 | 
			
		||||
      <div style={constantStyle}>
 | 
			
		||||
        {React.createElement(DynamicComponent, {
 | 
			
		||||
          value: fieldValue?.value || fieldValue,
 | 
			
		||||
          schema,
 | 
			
		||||
          onChange: handleChangeOfConstant,
 | 
			
		||||
        })}
 | 
			
		||||
      </div>
 | 
			
		||||
    ),
 | 
			
		||||
 | 
			
		||||
    // 表达式
 | 
			
		||||
    express: (
 | 
			
		||||
      <div style={expressStyle}>
 | 
			
		||||
        <Variable.TextArea
 | 
			
		||||
          value={fieldValue?.value}
 | 
			
		||||
          onChange={handleChangeOfExpress}
 | 
			
		||||
          scope={compatScope}
 | 
			
		||||
          style={textAreaStyle}
 | 
			
		||||
        />
 | 
			
		||||
        <>
 | 
			
		||||
          <span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
 | 
			
		||||
            {t('Syntax references')}:
 | 
			
		||||
          </span>
 | 
			
		||||
          <a href="https://formulajs.info/functions/" target="_blank" rel="noreferrer">
 | 
			
		||||
            Formula.js
 | 
			
		||||
          </a>
 | 
			
		||||
        </>
 | 
			
		||||
      </div>
 | 
			
		||||
    ),
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Input.Group compact>
 | 
			
		||||
      <Select
 | 
			
		||||
@ -29,44 +119,7 @@ export const ValueDynamicComponent = (props) => {
 | 
			
		||||
        <Option value="express">{t('Expression')}</Option>
 | 
			
		||||
        <Option value="empty">{t('Empty')}</Option>
 | 
			
		||||
      </Select>
 | 
			
		||||
      {mode === 'constant' ? (
 | 
			
		||||
        <div style={{ minWidth: 150, maxWidth: 430, marginLeft: 5 }}>
 | 
			
		||||
          {React.createElement(DynamicComponent, {
 | 
			
		||||
            value: fieldValue?.value || fieldValue,
 | 
			
		||||
            schema,
 | 
			
		||||
            onChange(value) {
 | 
			
		||||
              setValue({
 | 
			
		||||
                mode,
 | 
			
		||||
                value,
 | 
			
		||||
              });
 | 
			
		||||
            },
 | 
			
		||||
          })}
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : mode === 'express' ? (
 | 
			
		||||
        <div style={{ minWidth: 150, maxWidth: 430, fontSize: 13 }}>
 | 
			
		||||
          <Variable.TextArea
 | 
			
		||||
            value={fieldValue?.value}
 | 
			
		||||
            onChange={(value) => {
 | 
			
		||||
              const result = value.replaceAll(`${collectionName}.`, '').replaceAll('$system.', '').trim();
 | 
			
		||||
              setValue({
 | 
			
		||||
                mode,
 | 
			
		||||
                value,
 | 
			
		||||
                result,
 | 
			
		||||
              });
 | 
			
		||||
            }}
 | 
			
		||||
            scope={scope}
 | 
			
		||||
            style={{ minWidth: 460, marginRight: 15 }}
 | 
			
		||||
          />
 | 
			
		||||
          <>
 | 
			
		||||
            <span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
 | 
			
		||||
              {t('Syntax references')}:
 | 
			
		||||
            </span>
 | 
			
		||||
            <a href="https://formulajs.info/functions/" target="_blank" rel="noreferrer">
 | 
			
		||||
              Formula.js
 | 
			
		||||
            </a>
 | 
			
		||||
          </>
 | 
			
		||||
        </div>
 | 
			
		||||
      ) : null}
 | 
			
		||||
      {modeMap[mode]}
 | 
			
		||||
    </Input.Group>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user