diff --git a/docker/app-mariadb/docker-compose.yml b/docker/app-mariadb/docker-compose.yml index ff9d33b2b..e9c5678dd 100644 --- a/docker/app-mariadb/docker-compose.yml +++ b/docker/app-mariadb/docker-compose.yml @@ -17,6 +17,7 @@ services: - DB_USER=nocobase - DB_PASSWORD=nocobase - DB_TIMEZONE=+08:00 + - DB_UNDERSCORED=true volumes: - ./storage:/app/nocobase/storage ports: @@ -29,5 +30,7 @@ services: MYSQL_PASSWORD: nocobase MYSQL_ROOT_PASSWORD: nocobase restart: always + volumes: + - ./storage/db/mariadb:/var/lib/mysql networks: - nocobase \ No newline at end of file diff --git a/docker/app-mysql/docker-compose.yml b/docker/app-mysql/docker-compose.yml index 43351dfcd..c668c4d75 100644 --- a/docker/app-mysql/docker-compose.yml +++ b/docker/app-mysql/docker-compose.yml @@ -29,5 +29,7 @@ services: MYSQL_PASSWORD: nocobase MYSQL_ROOT_PASSWORD: nocobase restart: always + volumes: + - ./storage/db/mysql:/var/lib/mysql networks: - nocobase diff --git a/docker/app-postgres/docker-compose.yml b/docker/app-postgres/docker-compose.yml index d7e5c04e8..127e94ba6 100644 --- a/docker/app-postgres/docker-compose.yml +++ b/docker/app-postgres/docker-compose.yml @@ -28,5 +28,7 @@ services: POSTGRES_USER: nocobase POSTGRES_DB: nocobase POSTGRES_PASSWORD: nocobase + volumes: + - ./storage/db/postgres:/var/lib/postgresql/data networks: - nocobase \ No newline at end of file diff --git a/package.json b/package.json index abb86ba60..b8808b5be 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "scripts": { "nocobase": "nocobase", "pm": "nocobase pm", + "pm2": "nocobase pm2", "dev": "nocobase dev", "dev-server": "nocobase dev --server", "start": "nocobase start", diff --git a/packages/core/build/src/build.ts b/packages/core/build/src/build.ts index 46be1144b..d861bfbc3 100755 --- a/packages/core/build/src/build.ts +++ b/packages/core/build/src/build.ts @@ -9,6 +9,7 @@ import { getCjsPackages, getPresetsPackages, ROOT_PATH, + ESM_PACKAGES, } from './constant'; import { buildClient } from './buildClient'; import { buildCjs } from './buildCjs'; @@ -18,6 +19,7 @@ import { PkgLog, getPkgLog, toUnixPath, getPackageJson, getUserConfig, UserConfi import { getPackages } from './utils/getPackages'; import { Package } from '@lerna/package'; import { tarPlugin } from './tarPlugin' +import { buildEsm } from './buildEsm'; const BUILD_ERROR = 'build-error'; @@ -51,6 +53,8 @@ export async function build(pkgs: string[]) { if (clientCore) { await buildPackage(clientCore, 'es', buildClient); } + const esmPackages = cjsPackages.filter(pkg => ESM_PACKAGES.includes(pkg.name)); + await buildPackages(esmPackages, 'es', buildEsm); // plugins/*、samples/* await buildPackages(pluginPackages, 'dist', buildPlugin); diff --git a/packages/core/build/src/buildClient.ts b/packages/core/build/src/buildClient.ts index b8259bcdf..1ca0d7309 100644 --- a/packages/core/build/src/buildClient.ts +++ b/packages/core/build/src/buildClient.ts @@ -19,14 +19,14 @@ export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap } return true; }; - await buildEsm(cwd, userConfig, sourcemap, external, log); - await buildLib(cwd, userConfig, sourcemap, external, log); + await buildClientEsm(cwd, userConfig, sourcemap, external, log); + await buildClientLib(cwd, userConfig, sourcemap, external, log); await buildLocale(cwd, userConfig, log); } type External = (id: string) => boolean; -export function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean, external: External, log: PkgLog) { +function buildClientEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean, external: External, log: PkgLog) { log('build client esm'); const entry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/'); const outDir = path.resolve(cwd, 'es'); @@ -61,7 +61,7 @@ export function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean ); } -export async function buildLib( +async function buildClientLib( cwd: string, userConfig: UserConfig, sourcemap: boolean, diff --git a/packages/core/build/src/buildEsm.ts b/packages/core/build/src/buildEsm.ts new file mode 100644 index 000000000..851555549 --- /dev/null +++ b/packages/core/build/src/buildEsm.ts @@ -0,0 +1,66 @@ + +import path from 'path'; +import { PkgLog, UserConfig } from './utils'; +import { build as viteBuild } from 'vite'; +import fg from 'fast-glob'; + +export async function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean = false, log: PkgLog) { + log('build esm'); + + const indexEntry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/'); + const outDir = path.resolve(cwd, 'es'); + + await build(cwd, indexEntry, outDir, userConfig, sourcemap, log); + + const clientEntry = fg.sync(['src/client/index.ts', 'src/client.ts'], { cwd, absolute: true, onlyFiles: true })?.[0]?.replaceAll(/\\/g, '/'); + const clientOutDir = path.resolve(cwd, 'es/client'); + if (clientEntry) { + await build(cwd, clientEntry, clientOutDir, userConfig, sourcemap, log); + } + + const pkg = require(path.join(cwd, 'package.json')); + if (pkg.name === '@nocobase/test') { + const e2eEntry = path.join(cwd, 'src/e2e/index.ts').replaceAll(/\\/g, '/'); + const e2eOutDir = path.resolve(cwd, 'es/e2e'); + await build(cwd, e2eEntry, e2eOutDir, userConfig, sourcemap, log); + } +} + +function build(cwd: string, entry: string, outDir: string, userConfig: UserConfig, sourcemap: boolean = false, log: PkgLog) { + const cwdWin = cwd.replaceAll(/\\/g, '/'); + const cwdUnix = cwd.replaceAll(/\//g, '\\'); + const external = function (id: string) { + if (id.startsWith('.') || id.startsWith(cwdUnix) || id.startsWith(cwdWin)) { + return false; + } + return true; + }; + return viteBuild( + userConfig.modifyViteConfig({ + mode: process.env.NODE_ENV || 'production', + define: { + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'), + 'process.env.__TEST__': false, + 'process.env.__E2E__': process.env.__E2E__ ? true : false, + }, + build: { + minify: false, + outDir, + cssCodeSplit: true, + emptyOutDir: true, + sourcemap, + lib: { + entry, + formats: ['es'], + fileName: 'index', + }, + target: ['node16'], + rollupOptions: { + cache: true, + treeshake: true, + external, + }, + }, + }), + ); +} diff --git a/packages/core/build/src/constant.ts b/packages/core/build/src/constant.ts index f92072aff..0fb14c46c 100644 --- a/packages/core/build/src/constant.ts +++ b/packages/core/build/src/constant.ts @@ -39,6 +39,7 @@ export const getPresetsPackages = (packages: Package[]) => packages.filter((item) => item.location.startsWith(PRESETS_DIR)); export const CORE_APP = path.join(PACKAGES_PATH, 'core/app'); export const CORE_CLIENT = path.join(PACKAGES_PATH, 'core/client'); +export const ESM_PACKAGES = ['@nocobase/test']; export const CJS_EXCLUDE_PACKAGES = [ path.join(PACKAGES_PATH, 'core/build'), path.join(PACKAGES_PATH, 'core/cli'), diff --git a/packages/core/cli/src/commands/index.js b/packages/core/cli/src/commands/index.js index 48bfd57ee..2dc619c51 100644 --- a/packages/core/cli/src/commands/index.js +++ b/packages/core/cli/src/commands/index.js @@ -16,6 +16,7 @@ module.exports = (cli) => { require('./e2e')(cli); require('./clean')(cli); require('./doc')(cli); + require('./pm2')(cli); require('./test')(cli); require('./umi')(cli); require('./upgrade')(cli); diff --git a/packages/core/cli/src/commands/pm2.js b/packages/core/cli/src/commands/pm2.js new file mode 100644 index 000000000..423b04a90 --- /dev/null +++ b/packages/core/cli/src/commands/pm2.js @@ -0,0 +1,28 @@ +const chalk = require('chalk'); +const { Command } = require('commander'); +const { run, isDev } = require('../util'); + +/** + * + * @param {Command} cli + */ +module.exports = (cli) => { + cli + .command('pm2') + .allowUnknownOption() + .action(() => { + run('pm2', process.argv.slice(3)); + }); + cli + .command('pm2-restart') + .allowUnknownOption() + .action(() => { + run('pm2', ['restart', 'all']); + }); + cli + .command('pm2-stop') + .allowUnknownOption() + .action(() => { + run('pm2', ['stop', 'all']); + }); +}; diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index 938745e13..471ec5216 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -175,6 +175,9 @@ export class Application { return this.dataSourceManager.getDataSource(dataSource)?.collectionManager; } + /** + * @internal + */ getComposeProviders() { const Providers = compose(...this.providers)(BlankComponent); Providers.displayName = 'Providers'; @@ -280,6 +283,9 @@ export class Application { return React.createElement(this.getComponent(Component), props); } + /** + * @internal use addComponents({ SomeComponent }) instead + */ protected addComponent(component: ComponentType, name?: string) { const componentName = name || component.displayName || component.name; if (!componentName) { diff --git a/packages/core/client/src/application/PluginManager.ts b/packages/core/client/src/application/PluginManager.ts index 08f66cecd..ef5b85ab3 100644 --- a/packages/core/client/src/application/PluginManager.ts +++ b/packages/core/client/src/application/PluginManager.ts @@ -26,6 +26,9 @@ export class PluginManager { this.initPlugins = this.init(_plugins); } + /** + * @internal + */ async init(_plugins: PluginType[]) { await this.initStaticPlugins(_plugins); if (this.loadRemotePlugins) { @@ -84,6 +87,9 @@ export class PluginManager { return new plugin(opts, this.app); } + /** + * @internal + */ async load() { await this.initPlugins; diff --git a/packages/core/client/src/application/RouterManager.tsx b/packages/core/client/src/application/RouterManager.tsx index 5784463b2..405dc37aa 100644 --- a/packages/core/client/src/application/RouterManager.tsx +++ b/packages/core/client/src/application/RouterManager.tsx @@ -41,6 +41,9 @@ export class RouterManager { this.app = app; } + /** + * @internal + */ getRoutesTree(): RouteObject[] { type RouteTypeWithChildren = RouteType & { children?: RouteTypeWithChildren }; const routes: Record = {}; @@ -106,6 +109,9 @@ export class RouterManager { this.options.basename = basename; } + /** + * @internal + */ getRouterComponent() { const { type = 'browser', ...opts } = this.options; const Routers = { diff --git a/packages/core/client/src/application/schema-initializer/hooks/index.tsx b/packages/core/client/src/application/schema-initializer/hooks/index.tsx index 64cd3d30f..13dc41c1f 100644 --- a/packages/core/client/src/application/schema-initializer/hooks/index.tsx +++ b/packages/core/client/src/application/schema-initializer/hooks/index.tsx @@ -9,11 +9,17 @@ import { SchemaInitializerOptions } from '../types'; export * from './useAriaAttributeOfMenuItem'; +/** + * @internal + */ export function useSchemaInitializerMenuItems(items: any[], name?: string, onClick?: (args: any) => void) { const getMenuItems = useGetSchemaInitializerMenuItems(onClick); return useMemo(() => getMenuItems(items, name), [getMenuItems, items, name]); } +/** + * @internal + */ export function useGetSchemaInitializerMenuItems(onClick?: (args: any) => void) { const compile = useCompile(); diff --git a/packages/core/client/src/application/schema-initializer/hooks/useAriaAttributeOfMenuItem.ts b/packages/core/client/src/application/schema-initializer/hooks/useAriaAttributeOfMenuItem.ts index 04b2779dc..b1f38193c 100644 --- a/packages/core/client/src/application/schema-initializer/hooks/useAriaAttributeOfMenuItem.ts +++ b/packages/core/client/src/application/schema-initializer/hooks/useAriaAttributeOfMenuItem.ts @@ -1,6 +1,9 @@ import { useMemo } from 'react'; import { useSchemaInitializerSubMenuContext } from '../components/SchemaInitializerSubMenu'; +/** + * @internal + */ export const useAriaAttributeOfMenuItem = () => { const { isInMenu } = useSchemaInitializerSubMenuContext(); // 在 Menu 中,每一项的 role 已经被标记为 menuitem 了,不需要再次标记; diff --git a/packages/core/client/src/application/schema-settings/components/SchemaSettingsWrapper.tsx b/packages/core/client/src/application/schema-settings/components/SchemaSettingsWrapper.tsx index 5d7f7ab7f..ff0e8e81e 100644 --- a/packages/core/client/src/application/schema-settings/components/SchemaSettingsWrapper.tsx +++ b/packages/core/client/src/application/schema-settings/components/SchemaSettingsWrapper.tsx @@ -7,6 +7,9 @@ import React from 'react'; import { useDesignable } from '../../../schema-component'; import { useField, useFieldSchema } from '@formily/react'; +/** + * @internal + */ export const SchemaSettingsWrapper: FC> = (props) => { const { items, Component = SchemaSettingsIcon, name, componentProps, style, ...others } = props; const { dn } = useDesignable(); diff --git a/packages/core/client/src/application/utils/globalDeps.ts b/packages/core/client/src/application/utils/globalDeps.ts index 3de6c4406..177e6f474 100644 --- a/packages/core/client/src/application/utils/globalDeps.ts +++ b/packages/core/client/src/application/utils/globalDeps.ts @@ -35,6 +35,9 @@ import * as nocobaseClient from '../../index'; import type { RequireJS } from './requirejs'; +/** + * @internal + */ export function defineGlobalDeps(requirejs: RequireJS) { // react requirejs.define('react', () => React); diff --git a/packages/core/client/src/application/utils/remotePlugins.ts b/packages/core/client/src/application/utils/remotePlugins.ts index adfcabc6c..c6f3f850f 100644 --- a/packages/core/client/src/application/utils/remotePlugins.ts +++ b/packages/core/client/src/application/utils/remotePlugins.ts @@ -3,12 +3,18 @@ import type { PluginData } from '../PluginManager'; import type { RequireJS } from './requirejs'; import type { DevDynamicImport } from '../Application'; +/** + * @internal + */ export function defineDevPlugins(plugins: Record) { Object.entries(plugins).forEach(([packageName, plugin]) => { window.define(`${packageName}/client`, () => plugin); }); } +/** + * @internal + */ export function definePluginClient(packageName: string) { window.define(`${packageName}/client`, ['exports', packageName], function (_exports: any, _pluginExports: any) { Object.defineProperty(_exports, '__esModule', { @@ -27,6 +33,9 @@ export function definePluginClient(packageName: string) { }); } +/** + * @internal + */ export function configRequirejs(requirejs: any, pluginData: PluginData[]) { requirejs.requirejs.config({ waitSeconds: 120, @@ -37,6 +46,9 @@ export function configRequirejs(requirejs: any, pluginData: PluginData[]) { }); } +/** + * @internal + */ export function processRemotePlugins(pluginData: PluginData[], resolve: (plugins: [string, typeof Plugin][]) => void) { return (...pluginModules: (typeof Plugin & { default?: typeof Plugin })[]) => { const res: [string, typeof Plugin][] = pluginModules @@ -58,6 +70,9 @@ export function processRemotePlugins(pluginData: PluginData[], resolve: (plugins }; } +/** + * @internal + */ export function getRemotePlugins( requirejs: any, pluginData: PluginData[] = [], @@ -80,6 +95,9 @@ interface GetPluginsOption { devDynamicImport?: DevDynamicImport; } +/** + * @internal + */ export async function getPlugins(options: GetPluginsOption): Promise> { const { requirejs, pluginData, devDynamicImport } = options; if (pluginData.length === 0) return []; diff --git a/packages/core/client/src/application/utils/requirejs.ts b/packages/core/client/src/application/utils/requirejs.ts index 9a0d77edf..10aeddfcf 100644 --- a/packages/core/client/src/application/utils/requirejs.ts +++ b/packages/core/client/src/application/utils/requirejs.ts @@ -10,6 +10,9 @@ export interface RequireJS { define: RequireDefine } +/** + * @internal + */ export function getRequireJs(): RequireJS { var requirejs, require, define; var req, s, head, baseElement, dataMain, src, diff --git a/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx b/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx index e5b1b8c69..d7d8afbed 100644 --- a/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx +++ b/packages/core/client/src/block-provider/BlockSchemaComponentProvider.tsx @@ -24,6 +24,13 @@ import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table import { useListBlockDecoratorProps } from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps'; import { useTableSelectorDecoratorProps } from '../modules/blocks/data-blocks/table-selector/hooks/useTableSelectorDecoratorProps'; import { useCollapseBlockDecoratorProps } from '../modules/blocks/filter-blocks/collapse/hooks/useCollapseBlockDecoratorProps'; +import { useFilterFormBlockProps } from '../modules/blocks/filter-blocks/form/hooks/useFilterFormBlockProps'; +import { useFilterFormBlockDecoratorProps } from '../modules/blocks/filter-blocks/form/hooks/useFilterFormBlockDecoratorProps'; +import { useGridCardBlockDecoratorProps } from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps'; +import { useDetailsProps } from '../modules/blocks/data-blocks/details-single/hooks/useDetailsProps'; +import { useDetailsWithPaginationProps } from '../modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationProps'; +import { useDetailsDecoratorProps } from '../modules/blocks/data-blocks/details-single/hooks/useDetailsDecoratorProps'; +import { useDetailsWithPaginationDecoratorProps } from '../modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationDecoratorProps'; // TODO: delete this, replaced by `BlockSchemaComponentPlugin` export const BlockSchemaComponentProvider: React.FC = (props) => { @@ -51,6 +58,10 @@ export const BlockSchemaComponentProvider: React.FC = (props) => { useEditFormBlockProps, useFormFieldProps, useDetailsBlockProps, + useDetailsProps, + useDetailsWithPaginationProps, + useDetailsDecoratorProps, + useDetailsWithPaginationDecoratorProps, useTableFieldProps, useTableBlockProps, useTableSelectorProps, @@ -58,6 +69,9 @@ export const BlockSchemaComponentProvider: React.FC = (props) => { useListBlockDecoratorProps, useTableSelectorDecoratorProps, useCollapseBlockDecoratorProps, + useFilterFormBlockProps, + useFilterFormBlockDecoratorProps, + useGridCardBlockDecoratorProps, }} > {props.children} @@ -102,6 +116,10 @@ export class BlockSchemaComponentPlugin extends Plugin { useEditFormBlockProps, useFormFieldProps, useDetailsBlockProps, + useDetailsProps, + useDetailsWithPaginationProps, + useDetailsDecoratorProps, + useDetailsWithPaginationDecoratorProps, useTableFieldProps, useTableBlockProps, useTableSelectorProps, @@ -109,6 +127,9 @@ export class BlockSchemaComponentPlugin extends Plugin { useListBlockDecoratorProps, useTableSelectorDecoratorProps, useCollapseBlockDecoratorProps, + useFilterFormBlockProps, + useFilterFormBlockDecoratorProps, + useGridCardBlockDecoratorProps, }); } } diff --git a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx index 97edf2075..a0abf1c1b 100644 --- a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx +++ b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx @@ -2,11 +2,12 @@ import { createForm } from '@formily/core'; import { useField } from '@formily/react'; import { Spin } from 'antd'; import _ from 'lodash'; -import React, { createContext, useContext, useEffect, useMemo } from 'react'; +import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react'; import { useCollectionParentRecord } from '../data-source/collection-record/CollectionRecordProvider'; import { RecordProvider } from '../record-provider'; import { BlockProvider, useBlockRequestContext } from './BlockProvider'; import { useParsedFilter } from './hooks'; +import { withDynamicSchemaProps } from '../application/hoc/withDynamicSchemaProps'; export const DetailsBlockContext = createContext({}); DetailsBlockContext.displayName = 'DetailsBlockContext'; @@ -24,6 +25,7 @@ const InternalDetailsBlockProvider = (props) => { const { resource, service } = useBlockRequestContext(); const parentRecord = useCollectionParentRecord(); const currentRecord = (action === 'list' ? service?.data?.data?.[0] : service?.data?.data) || {}; + const formBlockRef = useRef(); const detailsBLockValue = useMemo(() => { return { action, @@ -31,6 +33,7 @@ const InternalDetailsBlockProvider = (props) => { field, service, resource, + formBlockRef, }; }, [action, field, form, resource, service]); @@ -50,20 +53,22 @@ const InternalDetailsBlockProvider = (props) => { return ( - - {props.children} - +
+ + {props.children} + +
); }; -export const DetailsBlockProvider = (props) => { +export const DetailsBlockProvider = withDynamicSchemaProps((props) => { return ( ); -}; +}); /** * @deprecated @@ -72,6 +77,11 @@ export const useDetailsBlockContext = () => { return useContext(DetailsBlockContext); }; +/** + * @deprecated + * 即将废弃,请用 useDetailsWithPaginationProps 或者 useDetailsProps + * @returns + */ export const useDetailsBlockProps = () => { const ctx = useDetailsBlockContext(); useEffect(() => { diff --git a/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx b/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx index 850c04e7a..b54c9433a 100644 --- a/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx +++ b/packages/core/client/src/block-provider/FilterFormBlockProvider.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { DatePickerProvider } from '../schema-component'; import { DefaultValueProvider } from '../schema-settings'; import { FormBlockProvider } from './FormBlockProvider'; +import { withDynamicSchemaProps } from '../application/hoc/withDynamicSchemaProps'; -export const FilterFormBlockProvider = (props) => { +export const FilterFormBlockProvider = withDynamicSchemaProps((props) => { return ( false}> @@ -11,4 +12,4 @@ export const FilterFormBlockProvider = (props) => { ); -}; +}); diff --git a/packages/core/client/src/block-provider/index.tsx b/packages/core/client/src/block-provider/index.tsx index 422b36faf..9074b0d19 100644 --- a/packages/core/client/src/block-provider/index.tsx +++ b/packages/core/client/src/block-provider/index.tsx @@ -6,4 +6,5 @@ export * from './FormFieldProvider'; export * from './TableBlockProvider'; export * from './TableFieldProvider'; export * from './TableSelectorProvider'; +export * from './DetailsBlockProvider'; export * from './hooks'; diff --git a/packages/core/client/src/collection-manager/interfaces/uuid.ts b/packages/core/client/src/collection-manager/interfaces/uuid.ts index a934d06d3..7dbfc0dd7 100644 --- a/packages/core/client/src/collection-manager/interfaces/uuid.ts +++ b/packages/core/client/src/collection-manager/interfaces/uuid.ts @@ -17,7 +17,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface { 'x-validator': 'uuid', }, }; - availableTypes = ['string', 'uid']; + availableTypes = ['string', 'uid', 'uuid']; properties = { 'uiSchema.title': { type: 'string', diff --git a/packages/core/client/src/collection-manager/mixins/InheritanceCollectionMixin.ts b/packages/core/client/src/collection-manager/mixins/InheritanceCollectionMixin.ts index 3f9549c27..2d489bd11 100644 --- a/packages/core/client/src/collection-manager/mixins/InheritanceCollectionMixin.ts +++ b/packages/core/client/src/collection-manager/mixins/InheritanceCollectionMixin.ts @@ -110,7 +110,7 @@ export class InheritanceCollectionMixin extends Collection { } // override Collection - getFieldsMap() { + protected getFieldsMap() { if (this.fieldsMap) { return this.fieldsMap; } diff --git a/packages/core/client/src/common/useFieldComponentName.tsx b/packages/core/client/src/common/useFieldComponentName.tsx index a20cbca9a..f993220b0 100644 --- a/packages/core/client/src/common/useFieldComponentName.tsx +++ b/packages/core/client/src/common/useFieldComponentName.tsx @@ -2,19 +2,7 @@ import { Field } from '@formily/core'; import { useField, useFieldSchema } from '@formily/react'; import { useIsFileField } from '../schema-component/antd/form-item/FormItem.Settings'; import { useColumnSchema } from '../schema-component/antd/table-v2/Table.Column.Decorator'; -import { useCollectionManager_deprecated, useCollection_deprecated } from '../collection-manager'; - -/** - * 获取字段相关的配置信息 - * @returns - */ -function useCollectionField() { - const { getCollectionJoinField } = useCollectionManager_deprecated(); - const { getField } = useCollection_deprecated(); - const fieldSchema = useFieldSchema(); - const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']); - return collectionField; -} +import { useCollectionField } from '../data-source'; export function useFieldComponentName(): string { const { fieldSchema: tableColumnSchema, collectionField: tableColumnField } = useColumnSchema(); diff --git a/packages/core/client/src/data-source/collection-field/CollectionField.tsx b/packages/core/client/src/data-source/collection-field/CollectionField.tsx index 2896d3563..514cfa31c 100644 --- a/packages/core/client/src/data-source/collection-field/CollectionField.tsx +++ b/packages/core/client/src/data-source/collection-field/CollectionField.tsx @@ -13,7 +13,10 @@ type Props = { children?: React.ReactNode; }; -// TODO: 初步适配 +/** + * TODO: 初步适配 + * @internal + */ export const CollectionFieldInternalField: React.FC = (props: Props) => { const { component } = props; const compile = useCompile(); diff --git a/packages/core/client/src/data-source/collection-field/CollectionFieldProvider.tsx b/packages/core/client/src/data-source/collection-field/CollectionFieldProvider.tsx index 22724c1db..bf186bd9f 100644 --- a/packages/core/client/src/data-source/collection-field/CollectionFieldProvider.tsx +++ b/packages/core/client/src/data-source/collection-field/CollectionFieldProvider.tsx @@ -22,7 +22,7 @@ export const CollectionFieldProvider: FC = (props) const collectionManager = useCollectionManager(); const value = useMemo(() => { - if (!collection || allowNull) return null; + if (!collection) return null; const field = fieldSchema?.['x-component-props']?.['field']; return ( collectionManager.getCollectionField(fieldSchema?.['x-collection-field']) || @@ -31,7 +31,7 @@ export const CollectionFieldProvider: FC = (props) ); }, [collection, fieldSchema, name, collectionManager]); - if (!collection || allowNull) { + if (!value && allowNull) { return <>{children}; } diff --git a/packages/core/client/src/data-source/collection/Collection.ts b/packages/core/client/src/data-source/collection/Collection.ts index 5f83bcaea..27763b019 100644 --- a/packages/core/client/src/data-source/collection/Collection.ts +++ b/packages/core/client/src/data-source/collection/Collection.ts @@ -234,7 +234,8 @@ export class Collection { getFields(predicate?: GetCollectionFieldPredicate) { return predicate ? filter(this.fields, predicate) : this.fields; } - getFieldsMap() { + + protected getFieldsMap() { if (!this.fieldsMap) { this.fieldsMap = this.getFields().reduce((memo, field) => { memo[field.name] = field; diff --git a/packages/core/client/src/data-source/collection/CollectionManager.ts b/packages/core/client/src/data-source/collection/CollectionManager.ts index 95bdf8fa1..e1b0b851e 100644 --- a/packages/core/client/src/data-source/collection/CollectionManager.ts +++ b/packages/core/client/src/data-source/collection/CollectionManager.ts @@ -131,6 +131,9 @@ export class CollectionManager { return this.getCollection(collectionName)?.getFields(predicate) || []; } + /** + * @internal + */ clone(collections: CollectionOptions[] = []) { const collectionManager = new CollectionManager([], this.dataSource); diff --git a/packages/core/client/src/data-source/collection/utils.ts b/packages/core/client/src/data-source/collection/utils.ts index 84a2fd013..a078ff872 100644 --- a/packages/core/client/src/data-source/collection/utils.ts +++ b/packages/core/client/src/data-source/collection/utils.ts @@ -1,6 +1,9 @@ import type { Application } from '../../application/Application'; import type { CollectionOptions } from './Collection'; +/** + * @internal + */ export const collectionTransform = (collection: CollectionOptions, app: Application) => { const { rawTitle, title, fields = [], ...rest } = collection; return { @@ -26,6 +29,9 @@ export const collectionTransform = (collection: CollectionOptions, app: Applicat }; }; +/** + * @internal + */ export function applyMixins(Cls: any, mixins: any[], options?: any, collectionManager?: any) { const instance = new Cls(options, collectionManager); mixins.forEach((MixinClass) => { diff --git a/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx b/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx index d92c582d4..33c3d8aec 100644 --- a/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx +++ b/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx @@ -1,7 +1,7 @@ import { App, Button, Result, Typography } from 'antd'; import React, { FC, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { CardItem, useCompile, useDesignable } from '../../schema-component'; +import { CardItem, EllipsisWithTooltip, useCompile, useDesignable } from '../../schema-component'; import { useDataSource } from '../data-source/DataSourceProvider'; import { useDataSourceManager } from '../data-source'; import { DEFAULT_DATA_SOURCE_KEY } from '../../data-source/data-source/DataSourceManager'; @@ -15,6 +15,9 @@ export interface CollectionDeletedPlaceholderProps { const { Text } = Typography; +/** + * @internal + */ export const CollectionDeletedPlaceholder: FC = ({ type, name, message }) => { const { designable, dn } = useDesignable(); const { modal } = App.useApp(); @@ -63,7 +66,11 @@ export const CollectionDeletedPlaceholder: FC if (designable) { if (type === 'Field') { - return {messageValue}; + return ( + + {messageValue} + + ); } return ( diff --git a/packages/core/client/src/data-source/components/DataSourceApplicationProvider.tsx b/packages/core/client/src/data-source/components/DataSourceApplicationProvider.tsx index c2b754691..4327f647d 100644 --- a/packages/core/client/src/data-source/components/DataSourceApplicationProvider.tsx +++ b/packages/core/client/src/data-source/components/DataSourceApplicationProvider.tsx @@ -10,6 +10,9 @@ interface DataSourceApplicationProviderProps extends CollectionManagerProviderPr dataSourceManager: DataSourceManager; } +/** + * @internal + */ export const DataSourceApplicationProvider: FC = ({ children, dataSourceManager, diff --git a/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx b/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx index 6936566d6..e4067ffb8 100644 --- a/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx +++ b/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx @@ -103,6 +103,9 @@ export interface DataBlockContextValue { export const DataBlockContext = createContext>({} as any); DataBlockContext.displayName = 'DataBlockContext'; +/** + * @internal + */ export const AssociationOrCollectionProvider = (props: { collection: string | CollectionOptions; association: string; diff --git a/packages/core/client/src/data-source/data-block/DataBlockRequestProvider.tsx b/packages/core/client/src/data-source/data-block/DataBlockRequestProvider.tsx index c8d361d61..9af56e19b 100644 --- a/packages/core/client/src/data-source/data-block/DataBlockRequestProvider.tsx +++ b/packages/core/client/src/data-source/data-block/DataBlockRequestProvider.tsx @@ -7,6 +7,7 @@ import { AllDataBlockProps, useDataBlockProps } from './DataBlockProvider'; import { useDataBlockResource } from './DataBlockResourceProvider'; import { useDataSourceHeaders } from '../utils'; import { useDataLoadingMode } from '../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem'; +import { useCollection, useCollectionManager } from '../collection'; export const BlockRequestContext = createContext>(null); BlockRequestContext.displayName = 'BlockRequestContext'; @@ -55,6 +56,7 @@ function useCurrentRequest(options: Omit) { function useParentRequest(options: Omit) { const { sourceId, association, parentRecord } = options; const api = useAPIClient(); + const cm = useCollectionManager(); const dataBlockProps = useDataBlockProps(); const headers = useDataSourceHeaders(dataBlockProps.dataSource); return useRequest( @@ -63,8 +65,12 @@ function useParentRequest(options: Omit) { if (!association) return Promise.resolve({ data: undefined }); // "association": "Collection.Field" const arr = association.split('.'); - // :get/ - const url = `${arr[0]}:get/${sourceId}`; + const field = cm.getCollectionField(association); + const isM2O = field.interface === 'm2o'; + const filterTargetKey = cm.getCollection(arr[0]).getOption('filterTargetKey'); + const filterKey = isM2O ? filterTargetKey : field.sourceKey; + // :get?filter[filterKey]=sourceId + const url = `${arr[0]}:get?filter[${filterKey}]=${sourceId}`; const res = await api.request({ url, headers }); return res.data; }, @@ -88,6 +94,7 @@ export const BlockRequestProvider: FC = ({ children }) => { requestOptions, requestService, } = props; + const currentRequest = useCurrentRequest<{ data: any }>({ action, sourceId, diff --git a/packages/core/client/src/data-source/data-source/DataSource.ts b/packages/core/client/src/data-source/data-source/DataSource.ts index 6a991f455..2787f98a4 100644 --- a/packages/core/client/src/data-source/data-source/DataSource.ts +++ b/packages/core/client/src/data-source/data-source/DataSource.ts @@ -83,6 +83,9 @@ export abstract class DataSource { } } +/** + * @internal + */ export class LocalDataSource extends DataSource { getDataSource() { return { diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsActionInitializers.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsActionInitializers.tsx index 15ace51a0..41adedbfb 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsActionInitializers.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsActionInitializers.tsx @@ -53,6 +53,10 @@ export const detailsActionInitializers_deprecated = new CompatibleSchemaInitiali ], }); +/** + * @deprecated + * 已弃用,请使用 readPrettyFormActionInitializers 代替 + */ export const detailsActionInitializers = new CompatibleSchemaInitializer( { name: 'detailsWithPaging:configureActions', diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsBlockInitializer.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsBlockInitializer.tsx index 4c5bb13ca..11abe4a9b 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsBlockInitializer.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/DetailsBlockInitializer.tsx @@ -2,9 +2,9 @@ import { TableOutlined } from '@ant-design/icons'; import React from 'react'; import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { useCollectionManager_deprecated } from '../../../../collection-manager'; -import { createDetailsBlockSchema } from '../../../../schema-initializer/utils'; import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer'; import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection'; +import { createDetailsWithPaginationUISchema } from './createDetailsWithPaginationUISchema'; export const DetailsBlockInitializer = ({ filterCollections, @@ -51,15 +51,14 @@ export const DetailsBlockInitializer = ({ const { item } = options; const collection = getCollection(item.name, item.dataSource); - const schema = createDetailsBlockSchema({ - collection: item.name, + const schema = createDetailsWithPaginationUISchema({ + collectionName: item.name, dataSource: item.dataSource, rowKey: collection.filterTargetKey || 'id', - actionInitializers: + hideActionInitializer: !( (collection.template !== 'view' || collection?.writableView) && - collection.template !== 'sql' && - 'detailsWithPaging:configureActions', - settings: 'blockSettings:multiDataDetails', + collection.template !== 'sql' + ), }); insert(schema); }} diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/__tests__/createDetailsBlockWithPagingUISchema.test.ts b/packages/core/client/src/modules/blocks/data-blocks/details-multi/__tests__/createDetailsBlockWithPagingUISchema.test.ts new file mode 100644 index 000000000..2bfc87976 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/__tests__/createDetailsBlockWithPagingUISchema.test.ts @@ -0,0 +1,69 @@ +import { createDetailsWithPaginationUISchema } from '../createDetailsWithPaginationUISchema'; + +vi.mock('@formily/shared', () => ({ + uid: vi.fn().mockReturnValue('mocked-uid'), +})); + +describe('createDetailsBlockWithPaginationUISchema', () => { + it('should return the correct schema', () => { + const schema = createDetailsWithPaginationUISchema({ + collectionName: 'users', + dataSource: 'users', + rowKey: 'id', + }); + + expect(schema).toMatchInlineSnapshot(` + { + "properties": { + "mocked-uid": { + "properties": { + "grid": { + "properties": {}, + "type": "void", + "x-component": "Grid", + "x-initializer": "details:configureFields", + }, + "mocked-uid": { + "properties": {}, + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "style": { + "marginBottom": 24, + }, + }, + "x-initializer": "details:configureActions", + }, + "pagination": { + "type": "void", + "x-component": "Pagination", + "x-use-component-props": "useDetailsPaginationProps", + }, + }, + "type": "void", + "x-component": "Details", + "x-read-pretty": true, + "x-use-component-props": "useDetailsWithPaginationProps", + }, + }, + "type": "void", + "x-acl-action": "users:view", + "x-component": "CardItem", + "x-decorator": "DetailsBlockProvider", + "x-decorator-props": { + "action": "list", + "association": undefined, + "collection": "users", + "dataSource": "users", + "params": { + "pageSize": 1, + }, + "readPretty": true, + }, + "x-settings": "blockSettings:detailsWithPagination", + "x-toolbar": "BlockSchemaToolbar", + "x-use-decorator-props": "useDetailsWithPaginationDecoratorProps", + } + `); + }); +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/createDetailsWithPaginationUISchema.ts b/packages/core/client/src/modules/blocks/data-blocks/details-multi/createDetailsWithPaginationUISchema.ts new file mode 100644 index 000000000..009162592 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/createDetailsWithPaginationUISchema.ts @@ -0,0 +1,70 @@ +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; + +export function createDetailsWithPaginationUISchema(options: { + dataSource: string; + rowKey: string; + collectionName?: string; + association?: string; + templateSchema?: ISchema; + hideActionInitializer?: boolean; +}): ISchema { + const { collectionName, dataSource, association, templateSchema, hideActionInitializer, rowKey } = options; + const resourceName = association || collectionName; + + if (!dataSource || !rowKey) { + throw new Error('dataSource and rowKey are required'); + } + + return { + type: 'void', + 'x-acl-action': `${resourceName}:view`, + 'x-decorator': 'DetailsBlockProvider', + 'x-use-decorator-props': 'useDetailsWithPaginationDecoratorProps', + 'x-decorator-props': { + dataSource, + collection: collectionName, + association, + readPretty: true, + action: 'list', + params: { + pageSize: 1, + }, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:detailsWithPagination', + 'x-component': 'CardItem', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Details', + 'x-read-pretty': true, + 'x-use-component-props': 'useDetailsWithPaginationProps', + properties: { + [uid()]: { + type: 'void', + 'x-initializer': hideActionInitializer ? undefined : 'details:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + properties: {}, + }, + grid: templateSchema || { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'details:configureFields', + properties: {}, + }, + pagination: { + type: 'void', + 'x-component': 'Pagination', + 'x-use-component-props': 'useDetailsPaginationProps', + }, + }, + }, + }, + }; +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx new file mode 100644 index 000000000..adea06e58 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx @@ -0,0 +1,216 @@ +import { ArrayItems } from '@formily/antd-v5'; +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import { useTranslation } from 'react-i18next'; +import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; +import { useFormBlockContext } from '../../../../block-provider'; +import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockProvider'; +import { useCollection_deprecated, useSortFields } from '../../../../collection-manager'; +import { removeNullCondition, useDesignable } from '../../../../schema-component'; +import { SchemaSettingsBlockTitleItem, SchemaSettingsTemplate } from '../../../../schema-settings'; +import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope'; +import { setDataLoadingModeSettingsItem, useDataLoadingMode } from './setDataLoadingModeSettingsItem'; +import { SchemaSettingsItemType } from '../../../../application/schema-settings/types'; + +const commonItems: SchemaSettingsItemType[] = [ + { + name: 'title', + Component: SchemaSettingsBlockTitleItem, + }, + { + name: 'dataScope', + Component: SchemaSettingsDataScope, + useComponentProps() { + const { name } = useCollection_deprecated(); + const fieldSchema = useFieldSchema(); + const { form } = useFormBlockContext(); + const field = useField(); + const { service } = useDetailsBlockContext(); + const { dn } = useDesignable(); + const dataLoadingMode = useDataLoadingMode(); + return { + collectionName: name, + defaultFilter: fieldSchema?.['x-decorator-props']?.params?.filter || {}, + form, + onSubmit: ({ filter }) => { + filter = removeNullCondition(filter); + const params = field.decoratorProps.params || {}; + params.filter = filter; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + + if (dataLoadingMode === 'auto') { + service.run({ ...service.params?.[0], filter }); + } + + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }, + }; + }, + }, + { + name: 'sortingRules', + type: 'modal', + useComponentProps() { + const { name } = useCollection_deprecated(); + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const { service } = useDetailsBlockContext(); + const { dn } = useDesignable(); + const sortFields = useSortFields(name); + const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || []; + const sort = defaultSort?.map((item: string) => { + return item.startsWith('-') + ? { + field: item.substring(1), + direction: 'desc', + } + : { + field: item, + direction: 'asc', + }; + }); + return { + title: t('Set default sorting rules'), + components: { + ArrayItems, + }, + schema: { + type: 'object', + title: t('Set default sorting rules'), + properties: { + sort: { + type: 'array', + default: sort, + 'x-component': 'ArrayItems', + 'x-decorator': 'FormItem', + items: { + type: 'object', + properties: { + space: { + type: 'void', + 'x-component': 'Space', + properties: { + sort: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.SortHandle', + }, + field: { + type: 'string', + enum: sortFields, + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Select', + 'x-component-props': { + style: { + width: 260, + }, + }, + }, + direction: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Radio.Group', + 'x-component-props': { + optionType: 'button', + }, + enum: [ + { + label: t('ASC'), + value: 'asc', + }, + { + label: t('DESC'), + value: 'desc', + }, + ], + }, + remove: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.Remove', + }, + }, + }, + }, + }, + properties: { + add: { + type: 'void', + title: t('Add sort field'), + 'x-component': 'ArrayItems.Addition', + }, + }, + }, + }, + } as ISchema, + onSubmit({ sort }) { + const sortArr = sort.map((item) => { + return item.direction === 'desc' ? `-${item.field}` : item.field; + }); + const params = field.decoratorProps.params || {}; + params.sort = sortArr; + field.decoratorProps.params = params; + fieldSchema['x-decorator-props']['params'] = params; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + service.run({ ...service.params?.[0], sort: sortArr }); + }, + }; + }, + }, + setDataLoadingModeSettingsItem, + { + name: 'template', + Component: SchemaSettingsTemplate, + useComponentProps() { + const { name } = useCollection_deprecated(); + const fieldSchema = useFieldSchema(); + const defaultResource = + fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association; + return { + componentName: 'Details', + collectionName: name, + resourceName: defaultResource, + }; + }, + }, + { + name: 'divider', + type: 'divider', + }, + { + name: 'remove', + type: 'remove', + componentProps: { + removeParentsIfNoChildren: true, + breakRemoveOn: { + 'x-component': 'Grid', + }, + }, + }, +]; + +/** + * @deprecated + * 已弃用,请使用 detailsWithPaginationSettings 代替 + */ +export const multiDataDetailsBlockSettings = new SchemaSettings({ + name: 'blockSettings:multiDataDetails', + items: commonItems, +}); + +export const detailsWithPaginationSettings = new SchemaSettings({ + name: 'blockSettings:detailsWithPagination', + items: commonItems, +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationDecoratorProps.ts b/packages/core/client/src/modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationDecoratorProps.ts new file mode 100644 index 000000000..5d3935fde --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationDecoratorProps.ts @@ -0,0 +1,15 @@ +import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId'; + +export function useDetailsWithPaginationDecoratorProps(props) { + let sourceId; + + // association 的值是固定不变的,所以可以在条件中使用 hooks + if (props.association) { + // eslint-disable-next-line react-hooks/rules-of-hooks + sourceId = useDataBlockSourceId({ association: props.association }); + } + + return { + sourceId, + }; +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationProps.ts b/packages/core/client/src/modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationProps.ts new file mode 100644 index 000000000..675c97518 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/hooks/useDetailsWithPaginationProps.ts @@ -0,0 +1,5 @@ +import { useDetailsBlockProps } from '../../../../../block-provider/DetailsBlockProvider'; + +export function useDetailsWithPaginationProps() { + return useDetailsBlockProps(); +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/multiDataDetailsBlockSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-multi/multiDataDetailsBlockSettings.tsx deleted file mode 100644 index 8b52f5907..000000000 --- a/packages/core/client/src/modules/blocks/data-blocks/details-multi/multiDataDetailsBlockSettings.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { ArrayItems } from '@formily/antd-v5'; -import { ISchema, useField, useFieldSchema } from '@formily/react'; -import { useTranslation } from 'react-i18next'; -import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; -import { useFormBlockContext } from '../../../../block-provider'; -import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockProvider'; -import { useCollection_deprecated, useSortFields } from '../../../../collection-manager'; -import { removeNullCondition, useDesignable } from '../../../../schema-component'; -import { SchemaSettingsBlockTitleItem, SchemaSettingsTemplate } from '../../../../schema-settings'; -import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope'; -import { setDataLoadingModeSettingsItem, useDataLoadingMode } from './setDataLoadingModeSettingsItem'; - -export const multiDataDetailsBlockSettings = new SchemaSettings({ - name: 'blockSettings:multiDataDetails', - items: [ - { - name: 'title', - Component: SchemaSettingsBlockTitleItem, - }, - { - name: 'dataScope', - Component: SchemaSettingsDataScope, - useComponentProps() { - const { name } = useCollection_deprecated(); - const fieldSchema = useFieldSchema(); - const { form } = useFormBlockContext(); - const field = useField(); - const { service } = useDetailsBlockContext(); - const { dn } = useDesignable(); - const dataLoadingMode = useDataLoadingMode(); - return { - collectionName: name, - defaultFilter: fieldSchema?.['x-decorator-props']?.params?.filter || {}, - form, - onSubmit: ({ filter }) => { - filter = removeNullCondition(filter); - const params = field.decoratorProps.params || {}; - params.filter = filter; - field.decoratorProps.params = params; - fieldSchema['x-decorator-props']['params'] = params; - - if (dataLoadingMode === 'auto') { - service.run({ ...service.params?.[0], filter }); - } - - dn.emit('patch', { - schema: { - ['x-uid']: fieldSchema['x-uid'], - 'x-decorator-props': fieldSchema['x-decorator-props'], - }, - }); - }, - }; - }, - }, - { - name: 'sortingRules', - type: 'modal', - useComponentProps() { - const { name } = useCollection_deprecated(); - const { t } = useTranslation(); - const fieldSchema = useFieldSchema(); - const field = useField(); - const { service } = useDetailsBlockContext(); - const { dn } = useDesignable(); - const sortFields = useSortFields(name); - const defaultSort = fieldSchema?.['x-decorator-props']?.params?.sort || []; - const sort = defaultSort?.map((item: string) => { - return item.startsWith('-') - ? { - field: item.substring(1), - direction: 'desc', - } - : { - field: item, - direction: 'asc', - }; - }); - return { - title: t('Set default sorting rules'), - components: { - ArrayItems, - }, - schema: { - type: 'object', - title: t('Set default sorting rules'), - properties: { - sort: { - type: 'array', - default: sort, - 'x-component': 'ArrayItems', - 'x-decorator': 'FormItem', - items: { - type: 'object', - properties: { - space: { - type: 'void', - 'x-component': 'Space', - properties: { - sort: { - type: 'void', - 'x-decorator': 'FormItem', - 'x-component': 'ArrayItems.SortHandle', - }, - field: { - type: 'string', - enum: sortFields, - required: true, - 'x-decorator': 'FormItem', - 'x-component': 'Select', - 'x-component-props': { - style: { - width: 260, - }, - }, - }, - direction: { - type: 'string', - 'x-decorator': 'FormItem', - 'x-component': 'Radio.Group', - 'x-component-props': { - optionType: 'button', - }, - enum: [ - { - label: t('ASC'), - value: 'asc', - }, - { - label: t('DESC'), - value: 'desc', - }, - ], - }, - remove: { - type: 'void', - 'x-decorator': 'FormItem', - 'x-component': 'ArrayItems.Remove', - }, - }, - }, - }, - }, - properties: { - add: { - type: 'void', - title: t('Add sort field'), - 'x-component': 'ArrayItems.Addition', - }, - }, - }, - }, - } as ISchema, - onSubmit({ sort }) { - const sortArr = sort.map((item) => { - return item.direction === 'desc' ? `-${item.field}` : item.field; - }); - const params = field.decoratorProps.params || {}; - params.sort = sortArr; - field.decoratorProps.params = params; - fieldSchema['x-decorator-props']['params'] = params; - dn.emit('patch', { - schema: { - ['x-uid']: fieldSchema['x-uid'], - 'x-decorator-props': fieldSchema['x-decorator-props'], - }, - }); - service.run({ ...service.params?.[0], sort: sortArr }); - }, - }; - }, - }, - setDataLoadingModeSettingsItem, - { - name: 'template', - Component: SchemaSettingsTemplate, - useComponentProps() { - const { name } = useCollection_deprecated(); - const fieldSchema = useFieldSchema(); - const defaultResource = - fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association; - return { - componentName: 'Details', - collectionName: name, - resourceName: defaultResource, - }; - }, - }, - { - name: 'divider', - type: 'divider', - }, - { - name: 'remove', - type: 'remove', - componentProps: { - removeParentsIfNoChildren: true, - breakRemoveOn: { - 'x-component': 'Grid', - }, - }, - }, - ], -}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx index 9efcdcf26..6e87749ba 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx @@ -47,6 +47,7 @@ const AssociatedFields = () => { /** * @deprecated + * 已弃用,请使用 readPrettyFormItemInitializers 代替 */ export const readPrettyFormItemInitializers_deprecated = new CompatibleSchemaInitializer({ name: 'ReadPrettyFormItemInitializers', diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/RecordReadPrettyFormBlockInitializer.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-single/RecordReadPrettyFormBlockInitializer.tsx index 35782e788..6d6127fbb 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-single/RecordReadPrettyFormBlockInitializer.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/RecordReadPrettyFormBlockInitializer.tsx @@ -1,10 +1,11 @@ import { FormOutlined } from '@ant-design/icons'; import React, { useCallback } from 'react'; import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; -import { useBlockAssociationContext, useBlockRequestContext } from '../../../../block-provider'; +import { useBlockAssociationContext } from '../../../../block-provider'; import { useCollection_deprecated } from '../../../../collection-manager'; import { useSchemaTemplateManager } from '../../../../schema-templates'; -import { createDetailsBlockSchema, useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils'; +import { useRecordCollectionDataSourceItems } from '../../../../schema-initializer/utils'; +import { createDetailsUISchema } from './createDetailsUISchema'; export const RecordReadPrettyFormBlockInitializer = () => { const itemConfig = useSchemaInitializerItem(); @@ -24,29 +25,27 @@ export const RecordReadPrettyFormBlockInitializer = () => { }; export function useCreateSingleDetailsSchema() { - const itemConfig = useSchemaInitializerItem(); const { insert } = useSchemaInitializer(); const { getTemplateSchemaByMode } = useSchemaTemplateManager(); const association = useBlockAssociationContext(); - const { block } = useBlockRequestContext(); - const actionInitializers = - block !== 'TableField' ? itemConfig.actionInitializers || 'details:configureActions' : null; const templateWrap = useCallback( (templateSchema, options) => { const { item } = options; if (item.template.componentName === 'ReadPrettyFormItem') { - const blockSchema = createDetailsBlockSchema({ - actionInitializers, - association, - collection: item.collectionName || item.name, - dataSource: item.dataSource, - action: 'get', - useSourceId: '{{ useSourceIdFromParentRecord }}', - useParams: '{{ useParamsFromRecord }}', - template: templateSchema, - settings: 'blockSettings:singleDataDetails', - }); + const blockSchema = createDetailsUISchema( + association + ? { + association, + dataSource: item.dataSource, + templateSchema: templateSchema, + } + : { + collectionName: item.collectionName || item.name, + dataSource: item.dataSource, + templateSchema: templateSchema, + }, + ); if (item.mode === 'reference') { blockSchema['x-template-key'] = item.template.key; } @@ -55,7 +54,7 @@ export function useCreateSingleDetailsSchema() { return templateSchema; } }, - [actionInitializers, association], + [association], ); const createSingleDetailsSchema = useCallback( @@ -65,20 +64,21 @@ export function useCreateSingleDetailsSchema() { insert(templateWrap(template, { item })); } else { insert( - createDetailsBlockSchema({ - actionInitializers, - association, - collection: item.collectionName || item.name, - dataSource: item.dataSource, - action: 'get', - useSourceId: '{{ useSourceIdFromParentRecord }}', - useParams: '{{ useParamsFromRecord }}', - settings: 'blockSettings:singleDataDetails', - }), + createDetailsUISchema( + association + ? { + association, + dataSource: item.dataSource, + } + : { + collectionName: item.collectionName || item.name, + dataSource: item.dataSource, + }, + ), ); } }, - [actionInitializers, association, getTemplateSchemaByMode, insert, templateWrap], + [association, getTemplateSchemaByMode, insert, templateWrap], ); return { createSingleDetailsSchema, templateWrap }; diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/__tests__/createDetailsBlockWithoutPagingUISchema.test.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/__tests__/createDetailsBlockWithoutPagingUISchema.test.ts new file mode 100644 index 000000000..520789160 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/__tests__/createDetailsBlockWithoutPagingUISchema.test.ts @@ -0,0 +1,62 @@ +import { createDetailsUISchema } from '../createDetailsUISchema'; + +// Mock uid to always return a fixed value +vi.mock('@formily/shared', () => ({ + uid: () => 'fixed-uid', +})); + +describe('createDetailsBlockWithoutPagingUISchema', () => { + it('should create a valid schema with mandatory fields', () => { + const options = { + collectionName: 'users', + dataSource: 'usersDataSource', + }; + + const result = createDetailsUISchema(options); + expect(result).toMatchInlineSnapshot(` + { + "properties": { + "fixed-uid": { + "properties": { + "fixed-uid": { + "properties": {}, + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "style": { + "marginBottom": 24, + }, + }, + "x-initializer": "details:configureActions", + }, + "grid": { + "properties": {}, + "type": "void", + "x-component": "Grid", + "x-initializer": "details:configureFields", + }, + }, + "type": "void", + "x-component": "Details", + "x-read-pretty": true, + "x-use-component-props": "useDetailsProps", + }, + }, + "type": "void", + "x-acl-action": "users:get", + "x-component": "CardItem", + "x-decorator": "DetailsBlockProvider", + "x-decorator-props": { + "action": "get", + "association": undefined, + "collection": "users", + "dataSource": "usersDataSource", + "readPretty": true, + }, + "x-settings": "blockSettings:details", + "x-toolbar": "BlockSchemaToolbar", + "x-use-decorator-props": "useDetailsDecoratorProps", + } + `); + }); +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/createDetailsUISchema.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/createDetailsUISchema.ts new file mode 100644 index 000000000..963c489e8 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/createDetailsUISchema.ts @@ -0,0 +1,60 @@ +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; + +export function createDetailsUISchema(options: { + dataSource: string; + collectionName?: string; + association?: string; + templateSchema?: ISchema; +}): ISchema { + const { collectionName, dataSource, association, templateSchema } = options; + const resourceName = association || collectionName; + + if (!dataSource) { + throw new Error('dataSource are required'); + } + + return { + type: 'void', + 'x-acl-action': `${resourceName}:get`, + 'x-decorator': 'DetailsBlockProvider', + 'x-use-decorator-props': 'useDetailsDecoratorProps', + 'x-decorator-props': { + dataSource, + collection: collectionName, + association, + readPretty: true, + action: 'get', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:details', + 'x-component': 'CardItem', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Details', + 'x-read-pretty': true, + 'x-use-component-props': 'useDetailsProps', + properties: { + [uid()]: { + type: 'void', + 'x-initializer': 'details:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + properties: {}, + }, + grid: templateSchema || { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'details:configureFields', + properties: {}, + }, + }, + }, + }, + }; +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts new file mode 100644 index 000000000..999109fff --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts @@ -0,0 +1,56 @@ +import { useFieldSchema } from '@formily/react'; +import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; +import { useCollection_deprecated } from '../../../../collection-manager'; +import { SchemaSettingsBlockTitleItem, SchemaSettingsFormItemTemplate } from '../../../../schema-settings'; +import { SchemaSettingsItemType } from '../../../../application/schema-settings/types'; + +const commonItems: SchemaSettingsItemType[] = [ + { + name: 'title', + Component: SchemaSettingsBlockTitleItem, + }, + { + name: 'formItemTemplate', + Component: SchemaSettingsFormItemTemplate, + useComponentProps() { + const { name } = useCollection_deprecated(); + const fieldSchema = useFieldSchema(); + const defaultResource = + fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association; + return { + insertAdjacentPosition: 'beforeEnd', + componentName: 'ReadPrettyFormItem', + collectionName: name, + resourceName: defaultResource, + }; + }, + }, + { + name: 'divider', + type: 'divider', + }, + { + name: 'remove', + type: 'remove', + componentProps: { + removeParentsIfNoChildren: true, + breakRemoveOn: { + 'x-component': 'Grid', + }, + }, + }, +]; + +/** + * @deprecated + * 已弃用,请使用 detailsBlockSettings 代替 + */ +export const singleDataDetailsBlockSettings = new SchemaSettings({ + name: 'blockSettings:singleDataDetails', + items: commonItems, +}); + +export const detailsBlockSettings = new SchemaSettings({ + name: 'blockSettings:details', + items: commonItems, +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/hooks/useDetailsDecoratorProps.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/hooks/useDetailsDecoratorProps.ts new file mode 100644 index 000000000..71cd7e870 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/hooks/useDetailsDecoratorProps.ts @@ -0,0 +1,18 @@ +import { useParamsFromRecord } from '../../../../../block-provider/BlockProvider'; +import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId'; + +export function useDetailsDecoratorProps(props) { + const params = useParamsFromRecord(); + let sourceId; + + // association 的值是固定不变的,所以可以在条件中使用 hooks + if (props.association) { + // eslint-disable-next-line react-hooks/rules-of-hooks + sourceId = useDataBlockSourceId({ association: props.association }); + } + + return { + params, + sourceId, + }; +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/hooks/useDetailsProps.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/hooks/useDetailsProps.ts new file mode 100644 index 000000000..69bacd854 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/hooks/useDetailsProps.ts @@ -0,0 +1,5 @@ +import { useDetailsBlockProps } from '../../../../../block-provider/DetailsBlockProvider'; + +export function useDetailsProps() { + return useDetailsBlockProps(); +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/singleDataDetailsBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/singleDataDetailsBlockSettings.ts deleted file mode 100644 index c2bfdda96..000000000 --- a/packages/core/client/src/modules/blocks/data-blocks/details-single/singleDataDetailsBlockSettings.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { useFieldSchema } from '@formily/react'; -import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; -import { useCollection_deprecated } from '../../../../collection-manager'; -import { SchemaSettingsBlockTitleItem, SchemaSettingsFormItemTemplate } from '../../../../schema-settings'; - -export const singleDataDetailsBlockSettings = new SchemaSettings({ - name: 'blockSettings:singleDataDetails', - items: [ - { - name: 'title', - Component: SchemaSettingsBlockTitleItem, - }, - { - name: 'formItemTemplate', - Component: SchemaSettingsFormItemTemplate, - useComponentProps() { - const { name } = useCollection_deprecated(); - const fieldSchema = useFieldSchema(); - const defaultResource = - fieldSchema?.['x-decorator-props']?.resource || fieldSchema?.['x-decorator-props']?.association; - return { - insertAdjacentPosition: 'beforeEnd', - componentName: 'ReadPrettyFormItem', - collectionName: name, - resourceName: defaultResource, - }; - }, - }, - { - name: 'divider', - type: 'divider', - }, - { - name: 'remove', - type: 'remove', - componentProps: { - removeParentsIfNoChildren: true, - breakRemoveOn: { - 'x-component': 'Grid', - }, - }, - }, - ], -}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts index 738b7d6a1..836d3847a 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts @@ -9,7 +9,7 @@ import { oneTableBlockWithAddNewAndViewAndEditAndBasicFields, test, } from '@nocobase/test/e2e'; -import { T2165, T2174, T3251 } from './templatesOfBug'; +import { T2165, T2174, T3251, T3806 } from './templatesOfBug'; const clickOption = async (page: Page, optionName: string) => { await page.getByLabel('block-item-CardItem-general-form').hover(); @@ -254,6 +254,47 @@ test.describe('creation form block schema settings', () => { page.getByLabel('block-item-CollectionField-users-form-users.email-Email').getByRole('textbox'), ).toBeEditable(); }); + + // https://nocobase.height.app/T-3806 + test('after save as block template', async ({ page, mockPage }) => { + await mockPage(T3806).goto(); + + // 1. 一开始联动规则应该正常 + await page + .getByLabel('block-item-CollectionField-users-form-users.nickname-Nickname') + .getByRole('textbox') + .fill('123'); + await expect( + page.getByLabel('block-item-CollectionField-users-form-users.username-Username').getByRole('textbox'), + ).toHaveValue('123'); + + try { + // 2. 将表单区块保存为模板后 + await page.getByLabel('block-item-CardItem-users-form').hover(); + await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover(); + await page.getByRole('menuitem', { name: 'Save as block template' }).click(); + await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.waitForTimeout(1000); + + // 3. 联动规则应该依然是正常的 + await page + .getByLabel('block-item-CollectionField-users-form-users.nickname-Nickname') + .getByRole('textbox') + .fill('456'); + await expect( + page.getByLabel('block-item-CollectionField-users-form-users.username-Username').getByRole('textbox'), + ).toHaveValue('456'); + } catch (err) { + throw err; + } finally { + // 4. 把创建的模板删除 + await page.goto('/admin/settings/ui-schema-storage'); + await page.getByLabel('Select all').check(); + await page.getByLabel('action-Action-Delete-destroy-').click(); + await page.getByRole('button', { name: 'OK', exact: true }).click(); + await expect(page.getByRole('row', { name: 'Users_Form' }).first()).toBeHidden(); + } + }); }); test('Save as block template & convert reference to duplicate', async ({ page, mockPage }) => { diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts index adf352e49..2f6ed4d94 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts @@ -6066,3 +6066,204 @@ export const T3529: PageConfig = { 'x-index': 1, }, }; + +export const T3806: PageConfig = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + '6w8tkq3665g': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + properties: { + '0k0yifwylj8': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + v5cdqnvyh3z: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + p9pszqbz1n5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'users:create', + 'x-decorator': 'FormBlockProvider', + 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'users', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + properties: { + '578cg3ao40s': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useCreateFormBlockProps', + properties: { + grid: { + 'x-uid': 'xpdqjz634mt', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + 'x-linkage-rules': [ + { + condition: { + $and: [], + }, + actions: [ + { + targetFields: ['username'], + operator: 'value', + value: { + mode: 'express', + value: '{{$nForm.nickname}}', + result: '{{$nForm.nickname}}', + }, + }, + ], + }, + ], + properties: { + gjck905cyws: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + i041myizzph: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + nickname: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.nickname', + 'x-component-props': {}, + 'x-uid': 'afnfoxl5cbi', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'a3o7l08zz6e', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'zoakp82dymw', + 'x-async': false, + 'x-index': 1, + }, + qffoo7lqua1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + nq0rbuti1d9: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + username: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.username', + 'x-component-props': {}, + 'x-uid': 'mq80n8nyuq8', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hf9bq4vjm80', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ua08izsl514', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + '006ip4mnr75': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'createForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + marginTop: 24, + }, + }, + 'x-uid': 'u6d026lia1x', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'p7ucl8ixcx4', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hz9xoui3o9d', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'j1q201o2wju', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 't0olm6ygvas', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'aq4i1y0nf00', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'px8uapyslvw', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/GridCardBlockInitializer.tsx b/packages/core/client/src/modules/blocks/data-blocks/grid-card/GridCardBlockInitializer.tsx index c483b9710..c0d4c2b9e 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/grid-card/GridCardBlockInitializer.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/GridCardBlockInitializer.tsx @@ -2,7 +2,7 @@ import { OrderedListOutlined } from '@ant-design/icons'; import React from 'react'; import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; import { useCollectionManager_deprecated } from '../../../../collection-manager'; -import { createGridCardBlockSchema } from '../../../../schema-initializer/utils'; +import { createGridCardBlockSchema } from './createGridCardBlockSchema'; import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer'; import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection'; @@ -48,10 +48,9 @@ export const GridCardBlockInitializer = ({ const collection = getCollection(item.name, item.dataSource); const schema = createGridCardBlockSchema({ - collection: item.name, + collectionName: item.name, dataSource: item.dataSource, rowKey: collection.filterTargetKey || 'id', - settings: 'blockSettings:gridCard', }); insert(schema); }} diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/__tests__/createGridCardBlockSchema.test.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/__tests__/createGridCardBlockSchema.test.ts new file mode 100644 index 000000000..11164efdc --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/__tests__/createGridCardBlockSchema.test.ts @@ -0,0 +1,158 @@ +import { createGridCardBlockSchema } from '../createGridCardBlockSchema'; + +describe('createGridCardBlockSchema', () => { + test('should return the correct schema', () => { + const options = { + collectionName: 'testCollection', + dataSource: 'testDataSource', + association: 'testAssociation', + templateSchema: { type: 'string' }, + rowKey: 'testRowKey', + }; + + const schema = createGridCardBlockSchema(options); + + expect(schema).toMatchInlineSnapshot(` + { + "properties": { + "actionBar": { + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "style": { + "marginBottom": "var(--nb-spacing)", + }, + }, + "x-initializer": "gridCard:configureActions", + }, + "list": { + "properties": { + "item": { + "properties": { + "actionBar": { + "type": "void", + "x-align": "left", + "x-component": "ActionBar", + "x-component-props": { + "layout": "one-column", + }, + "x-initializer": "gridCard:configureItemActions", + "x-use-component-props": "useGridCardActionBarProps", + }, + "grid": { + "type": "string", + }, + }, + "type": "object", + "x-component": "GridCard.Item", + "x-read-pretty": true, + "x-use-component-props": "useGridCardItemProps", + }, + }, + "type": "array", + "x-component": "GridCard", + "x-use-component-props": "useGridCardBlockProps", + }, + }, + "type": "void", + "x-acl-action": "testAssociation:view", + "x-component": "BlockItem", + "x-decorator": "GridCard.Decorator", + "x-decorator-props": { + "action": "list", + "association": "testAssociation", + "collection": "testCollection", + "dataSource": "testDataSource", + "params": { + "pageSize": 12, + }, + "readPretty": true, + "rowKey": "testRowKey", + "runWhenParamsChanged": true, + }, + "x-settings": "blockSettings:gridCard", + "x-toolbar": "BlockSchemaToolbar", + "x-use-component-props": "useGridCardBlockItemProps", + "x-use-decorator-props": "useGridCardBlockDecoratorProps", + } + `); + }); + + test('should return the correct schema when templateSchema is empty', () => { + const options = { + collectionName: 'testCollection', + dataSource: 'testDataSource', + association: 'testAssociation', + rowKey: 'testRowKey', + }; + + const schema = createGridCardBlockSchema(options); + + expect(schema).toMatchInlineSnapshot(` + { + "properties": { + "actionBar": { + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "style": { + "marginBottom": "var(--nb-spacing)", + }, + }, + "x-initializer": "gridCard:configureActions", + }, + "list": { + "properties": { + "item": { + "properties": { + "actionBar": { + "type": "void", + "x-align": "left", + "x-component": "ActionBar", + "x-component-props": { + "layout": "one-column", + }, + "x-initializer": "gridCard:configureItemActions", + "x-use-component-props": "useGridCardActionBarProps", + }, + "grid": { + "type": "void", + "x-component": "Grid", + "x-initializer": "details:configureFields", + }, + }, + "type": "object", + "x-component": "GridCard.Item", + "x-read-pretty": true, + "x-use-component-props": "useGridCardItemProps", + }, + }, + "type": "array", + "x-component": "GridCard", + "x-use-component-props": "useGridCardBlockProps", + }, + }, + "type": "void", + "x-acl-action": "testAssociation:view", + "x-component": "BlockItem", + "x-decorator": "GridCard.Decorator", + "x-decorator-props": { + "action": "list", + "association": "testAssociation", + "collection": "testCollection", + "dataSource": "testDataSource", + "params": { + "pageSize": 12, + }, + "readPretty": true, + "rowKey": "testRowKey", + "runWhenParamsChanged": true, + }, + "x-settings": "blockSettings:gridCard", + "x-toolbar": "BlockSchemaToolbar", + "x-use-component-props": "useGridCardBlockItemProps", + "x-use-decorator-props": "useGridCardBlockDecoratorProps", + } + `); + }); +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/createGridCardBlockSchema.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/createGridCardBlockSchema.ts new file mode 100644 index 000000000..3f69b8846 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/createGridCardBlockSchema.ts @@ -0,0 +1,81 @@ +import { ISchema } from '@formily/react'; + +export const createGridCardBlockSchema = (options: { + dataSource: string; + collectionName?: string; + association?: string; + templateSchema?: ISchema; + rowKey?: string; +}): ISchema => { + const { collectionName, association, templateSchema, dataSource, rowKey } = options; + const resourceName = association || collectionName; + + if (!dataSource) { + throw new Error('dataSource are required'); + } + + return { + type: 'void', + 'x-acl-action': `${resourceName}:view`, + 'x-decorator': 'GridCard.Decorator', + 'x-use-decorator-props': 'useGridCardBlockDecoratorProps', + 'x-decorator-props': { + collection: collectionName, + association, + dataSource, + readPretty: true, + action: 'list', + params: { + pageSize: 12, + }, + runWhenParamsChanged: true, + rowKey, + }, + 'x-component': 'BlockItem', + 'x-use-component-props': 'useGridCardBlockItemProps', + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:gridCard', + properties: { + actionBar: { + type: 'void', + 'x-initializer': 'gridCard:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + }, + list: { + type: 'array', + 'x-component': 'GridCard', + 'x-use-component-props': 'useGridCardBlockProps', + properties: { + item: { + type: 'object', + 'x-component': 'GridCard.Item', + 'x-read-pretty': true, + 'x-use-component-props': 'useGridCardItemProps', + properties: { + grid: templateSchema || { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'details:configureFields', + }, + actionBar: { + type: 'void', + 'x-align': 'left', + 'x-initializer': 'gridCard:configureItemActions', + 'x-component': 'ActionBar', + 'x-use-component-props': 'useGridCardActionBarProps', + 'x-component-props': { + layout: 'one-column', + }, + }, + }, + }, + }, + }, + }, + }; +}; diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps.ts new file mode 100644 index 000000000..1dac89fee --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps.ts @@ -0,0 +1,18 @@ +import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId'; +import { useGridCardBlockParams } from './useGridCardBlockParams'; + +export function useGridCardBlockDecoratorProps(props) { + const params = useGridCardBlockParams(props); + let sourceId; + + // 因为 association 是固定的,所以可以在条件中使用 hooks + if (props.association) { + // eslint-disable-next-line react-hooks/rules-of-hooks + sourceId = useDataBlockSourceId({ association: props.association }); + } + + return { + params, + sourceId, + }; +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockParams.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockParams.ts new file mode 100644 index 000000000..1edcac117 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockParams.ts @@ -0,0 +1,17 @@ +import { useParsedFilter } from '../../../../../block-provider/hooks/useParsedFilter'; +import { useMemo } from 'react'; + +export function useGridCardBlockParams(props) { + const { params } = props; + const { filter: parsedFilter } = useParsedFilter({ + filterOption: params?.filter, + }); + const paramsWithFilter = useMemo(() => { + return { + ...params, + filter: parsedFilter, + }; + }, [parsedFilter, params]); + + return paramsWithFilter; +} diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/FilterFormBlockInitializer.tsx b/packages/core/client/src/modules/blocks/filter-blocks/form/FilterFormBlockInitializer.tsx index 1842396f5..3263e610e 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/FilterFormBlockInitializer.tsx +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/FilterFormBlockInitializer.tsx @@ -1,7 +1,7 @@ import { FormOutlined } from '@ant-design/icons'; import React from 'react'; import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; -import { createFilterFormBlockSchema } from '../../../../schema-initializer/utils'; +import { createFilterFormBlockSchema } from './createFilterFormBlockSchema'; import { FilterBlockInitializer } from '../../../../schema-initializer/items/FilterBlockInitializer'; import { Collection, CollectionFieldOptions } from '../../../../data-source'; @@ -25,10 +25,9 @@ export const FilterFormBlockInitializer = ({ componentType={'FilterFormItem'} templateWrap={(templateSchema, { item }) => { const s = createFilterFormBlockSchema({ - template: templateSchema, + templateSchema: templateSchema, dataSource: item.dataSource, - collection: item.name || item.collectionName, - settings: 'blockSettings:filterForm', + collectionName: item.name || item.collectionName, }); if (item.template && item.mode === 'reference') { s['x-template-key'] = item.template.key; @@ -38,9 +37,8 @@ export const FilterFormBlockInitializer = ({ onCreateBlockSchema={({ item }) => { return insert( createFilterFormBlockSchema({ - collection: item.collectionName || item.name, + collectionName: item.collectionName || item.name, dataSource: item.dataSource, - settings: 'blockSettings:filterForm', }), ); }} diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/__tests__/createFilterFormBlockSchema.test.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/__tests__/createFilterFormBlockSchema.test.ts new file mode 100644 index 000000000..4fc5e833d --- /dev/null +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/__tests__/createFilterFormBlockSchema.test.ts @@ -0,0 +1,106 @@ +import { createFilterFormBlockSchema } from '../createFilterFormBlockSchema'; + +vi.mock('@formily/shared', () => { + return { + uid: () => 'mocked-uid', + }; +}); + +describe('createFilterFormBlockSchema', () => { + test('should return the correct schema', () => { + const schema = createFilterFormBlockSchema({ + collectionName: 'myCollection', + dataSource: 'myDataSource', + templateSchema: { type: 'string' }, + }); + + expect(schema).toMatchInlineSnapshot(` + { + "properties": { + "mocked-uid": { + "properties": { + "grid": { + "type": "string", + }, + "mocked-uid": { + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "layout": "one-column", + "style": { + "float": "right", + }, + }, + "x-initializer": "filterForm:configureActions", + }, + }, + "type": "void", + "x-component": "FormV2", + "x-use-component-props": "useFilterFormBlockProps", + }, + }, + "type": "void", + "x-component": "CardItem", + "x-decorator": "FilterFormBlockProvider", + "x-decorator-props": { + "collection": "myCollection", + "dataSource": "myDataSource", + }, + "x-filter-operators": {}, + "x-filter-targets": [], + "x-settings": "blockSettings:filterForm", + "x-toolbar": "BlockSchemaToolbar", + "x-use-decorator-props": "useFilterFormBlockDecoratorProps", + } + `); + }); + + test('should return the correct schema without templateSchema', () => { + const schema = createFilterFormBlockSchema({ + collectionName: 'myCollection', + dataSource: 'myDataSource', + }); + + expect(schema).toMatchInlineSnapshot(` + { + "properties": { + "mocked-uid": { + "properties": { + "grid": { + "type": "void", + "x-component": "Grid", + "x-initializer": "filterForm:configureFields", + }, + "mocked-uid": { + "type": "void", + "x-component": "ActionBar", + "x-component-props": { + "layout": "one-column", + "style": { + "float": "right", + }, + }, + "x-initializer": "filterForm:configureActions", + }, + }, + "type": "void", + "x-component": "FormV2", + "x-use-component-props": "useFilterFormBlockProps", + }, + }, + "type": "void", + "x-component": "CardItem", + "x-decorator": "FilterFormBlockProvider", + "x-decorator-props": { + "collection": "myCollection", + "dataSource": "myDataSource", + }, + "x-filter-operators": {}, + "x-filter-targets": [], + "x-settings": "blockSettings:filterForm", + "x-toolbar": "BlockSchemaToolbar", + "x-use-decorator-props": "useFilterFormBlockDecoratorProps", + } + `); + }); +}); diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/createFilterFormBlockSchema.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/createFilterFormBlockSchema.ts new file mode 100644 index 000000000..85ab058de --- /dev/null +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/createFilterFormBlockSchema.ts @@ -0,0 +1,57 @@ +import { ISchema } from '@formily/react'; +import { uid } from '@formily/shared'; + +export const createFilterFormBlockSchema = (options: { + collectionName: string; + dataSource: string; + templateSchema?: ISchema; +}) => { + const { collectionName, dataSource, templateSchema } = options; + + if (!collectionName || !dataSource) { + throw new Error('collectionName and dataSource are required'); + } + + const schema: ISchema = { + type: 'void', + 'x-decorator': 'FilterFormBlockProvider', + 'x-use-decorator-props': 'useFilterFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource, + collection: collectionName, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:filterForm', + 'x-component': 'CardItem', + // 保存当前筛选区块所能过滤的数据区块 + 'x-filter-targets': [], + // 用于存储用户设置的每个字段的运算符,目前仅筛选表单区块支持自定义 + 'x-filter-operators': {}, + properties: { + [uid()]: { + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useFilterFormBlockProps', + properties: { + grid: templateSchema || { + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'filterForm:configureFields', + }, + [uid()]: { + type: 'void', + 'x-initializer': 'filterForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + float: 'right', + }, + }, + }, + }, + }, + }, + }; + return schema; +}; diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/hooks/useFilterFormBlockDecoratorProps.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/hooks/useFilterFormBlockDecoratorProps.ts new file mode 100644 index 000000000..37f6d0fc8 --- /dev/null +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/hooks/useFilterFormBlockDecoratorProps.ts @@ -0,0 +1 @@ +export function useFilterFormBlockDecoratorProps() {} diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/hooks/useFilterFormBlockProps.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/hooks/useFilterFormBlockProps.ts new file mode 100644 index 000000000..76ad7db2b --- /dev/null +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/hooks/useFilterFormBlockProps.ts @@ -0,0 +1,5 @@ +import { useFormBlockProps } from '../../../../../block-provider/FormBlockProvider'; + +export function useFilterFormBlockProps() { + return useFormBlockProps(); +} diff --git a/packages/core/client/src/modules/dialog/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/dialog/__e2e__/schemaInitializer.test.ts index 210eb77e0..0d7879d7f 100644 --- a/packages/core/client/src/modules/dialog/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/dialog/__e2e__/schemaInitializer.test.ts @@ -88,7 +88,7 @@ test.describe('where to open a popup and what can be added to it', () => { // 删除已创建的 blocks,腾出页面空间 // delete details block await page.getByText('GeneralConfigure actionsConfigure fields').hover(); - await page.getByLabel('designer-schema-settings-CardItem-blockSettings:singleDataDetails-general').hover(); + await page.getByLabel('designer-schema-settings-CardItem-blockSettings:details-general').hover(); await page.getByRole('menuitem', { name: 'Delete' }).click(); await page.getByRole('button', { name: 'OK', exact: true }).click(); // delete form block diff --git a/packages/core/client/src/modules/fields/component/CascadeSelect/cascadeSelectComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/CascadeSelect/cascadeSelectComponentFieldSettings.tsx index b3f9bac57..5d24fe83c 100644 --- a/packages/core/client/src/modules/fields/component/CascadeSelect/cascadeSelectComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/CascadeSelect/cascadeSelectComponentFieldSettings.tsx @@ -6,7 +6,7 @@ import { useFieldComponentName } from '../../../../common/useFieldComponentName' import { useDesignable, useFieldModeOptions, useIsAddNewForm } from '../../../../schema-component'; import { isSubMode } from '../../../../schema-component/antd/association-field/util'; import { useTitleFieldOptions } from '../../../../schema-component/antd/form-item/FormItem.Settings'; -import { useCollectionField } from '../utils'; +import { useCollectionField } from '../../../../data-source'; const fieldComponent: any = { name: 'fieldComponent', diff --git a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx index 6e5801303..a525d6ef8 100644 --- a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx @@ -9,7 +9,7 @@ import { useIsFieldReadPretty, useIsFormReadPretty, } from '../../../../schema-component/antd/form-item/FormItem.Settings'; -import { useCollectionField } from '../utils'; +import { useCollectionField } from '../../../../data-source'; import { useFormBlockType } from '../../../../block-provider'; const allowMultiple: any = { diff --git a/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx index 3ca9d45d1..e3ea77a52 100644 --- a/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx @@ -10,7 +10,7 @@ import { useTitleFieldOptions, } from '../../../../schema-component/antd/form-item/FormItem.Settings'; import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; -import { useCollectionField } from '../utils'; +import { useCollectionField } from '../../../../data-source'; export const titleField: any = { name: 'titleField', diff --git a/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx index dd99735fc..725c76b72 100644 --- a/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx @@ -6,7 +6,7 @@ import { useFieldComponentName } from '../../../../common/useFieldComponentName' import { useDesignable, useFieldModeOptions, useIsAddNewForm } from '../../../../schema-component'; import { isSubMode } from '../../../../schema-component/antd/association-field/util'; import { useIsFieldReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings'; -import { useCollectionField } from '../utils'; +import { useCollectionField } from '../../../../data-source'; import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; import { titleField } from '../Picker/recordPickerComponentFieldSettings'; diff --git a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx index f107afef3..5c4c8f860 100644 --- a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx @@ -23,7 +23,7 @@ import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSetti import { SchemaSettingsSortingRule } from '../../../../schema-settings/SchemaSettingsSortingRule'; import { useIsShowMultipleSwitch } from '../../../../schema-settings/hooks/useIsShowMultipleSwitch'; import { useLocalVariables, useVariables } from '../../../../variables'; -import { useCollectionField } from '../utils'; +import { useCollectionField } from '../../../../data-source'; const enableLink = { name: 'enableLink', diff --git a/packages/core/client/src/modules/fields/component/Tag/tagComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Tag/tagComponentFieldSettings.tsx index 3bc7db00a..6d5d9c69f 100644 --- a/packages/core/client/src/modules/fields/component/Tag/tagComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Tag/tagComponentFieldSettings.tsx @@ -8,7 +8,7 @@ import { isSubMode } from '../../../../schema-component/antd/association-field/u import { useTitleFieldOptions } from '../../../../schema-component/antd/form-item/FormItem.Settings'; import { useColorFields } from '../../../../schema-component/antd/table-v2/Table.Column.Designer'; import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; -import { useCollectionField } from '../utils'; +import { useCollectionField } from '../../../../data-source'; const enableLink = { name: 'enableLink', @@ -140,9 +140,12 @@ export const tagComponentFieldSettings = new SchemaSettings({ useComponentProps() { const { t } = useTranslation(); const field = useField(); - const fieldSchema = useFieldSchema(); + const schema = useFieldSchema(); + const targetCollectionField = useCollectionField(); + const { fieldSchema: tableColumnSchema, collectionField: tableColumnField } = useColumnSchema(); + const fieldSchema = tableColumnSchema || schema; + const collectionField = tableColumnField || targetCollectionField; const { dn } = useDesignable(); - const collectionField = useCollectionField(); const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection); return { title: t('Tag color field'), diff --git a/packages/core/client/src/modules/fields/component/utils.tsx b/packages/core/client/src/modules/fields/component/utils.tsx deleted file mode 100644 index 3c47f1758..000000000 --- a/packages/core/client/src/modules/fields/component/utils.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useFieldSchema } from '@formily/react'; -import { useCollection_deprecated, useCollectionManager_deprecated } from '../../../collection-manager'; - -/** - * 获取字段相关的配置信息 - * @returns - */ -export function useCollectionField() { - const { getCollectionJoinField } = useCollectionManager_deprecated(); - const { getField } = useCollection_deprecated(); - const fieldSchema = useFieldSchema(); - const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']); - return collectionField; -} diff --git a/packages/core/client/src/powered-by/index.tsx b/packages/core/client/src/powered-by/index.tsx index 949762aae..7dff2ef2a 100644 --- a/packages/core/client/src/powered-by/index.tsx +++ b/packages/core/client/src/powered-by/index.tsx @@ -33,7 +33,7 @@ export const PoweredBy = () => { dangerouslySetInnerHTML={{ __html: parseHTML( customBrandPlugin?.options?.options?.brand || - `Powered by NocoBase`, + `Powered by NocoBase`, { appVersion }, ), }} diff --git a/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx b/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx index 63a9b9a3e..01f47df86 100644 --- a/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx +++ b/packages/core/client/src/schema-component/antd/block-item/BlockItem.tsx @@ -5,9 +5,12 @@ import { SortableItem } from '../../common'; import { useDesigner, useProps } from '../../hooks'; import { useGetAriaLabelOfBlockItem } from './hooks/useGetAriaLabelOfBlockItem'; import { useFieldSchema } from '@formily/react'; +import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; -export const BlockItem: React.FC = (props) => { +export const BlockItem: React.FC = withDynamicSchemaProps((props) => { + // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const { className, children } = useProps(props); + const Designer = useDesigner(); const fieldSchema = useFieldSchema(); const { getAriaLabel } = useGetAriaLabelOfBlockItem(props.name); @@ -69,4 +72,4 @@ export const BlockItem: React.FC = (props) => { {children} ); -}; +}); diff --git a/packages/core/client/src/schema-component/antd/details/Details.tsx b/packages/core/client/src/schema-component/antd/details/Details.tsx index 06bf20f36..d1641a71e 100644 --- a/packages/core/client/src/schema-component/antd/details/Details.tsx +++ b/packages/core/client/src/schema-component/antd/details/Details.tsx @@ -3,8 +3,9 @@ import { FormV2 } from '../form-v2'; import _ from 'lodash'; import { Empty } from 'antd'; import { useDataBlockRequest } from '../../../data-source'; +import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; -export const Details = (props) => { +export const Details = withDynamicSchemaProps((props) => { const request = useDataBlockRequest(); if (!request?.loading && _.isEmpty(request?.data?.data)) { @@ -12,4 +13,4 @@ export const Details = (props) => { } return ; -}; +}); diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index f8d2d4a92..c228165d0 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -67,16 +67,15 @@ export const FormItem: any = observer( }, ); }, [showTitle]); - const fieldSchema = useFieldSchema(); return ( - - + + - - + + ); }, { displayName: 'FormItem' }, diff --git a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx index 7890fb896..24489b847 100644 --- a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx +++ b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx @@ -19,6 +19,7 @@ import { getInnermostKeyAndValue, getTargetField } from '../../common/utils/uitl import { useProps } from '../../hooks/useProps'; import { collectFieldStateOfLinkageRules, getTempFieldState } from './utils'; import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; +import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider'; export interface FormProps { [key: string]: any; @@ -87,6 +88,7 @@ const WithForm = (props: WithFormProps) => { const { setFormValueChanged } = useActionContext(); const variables = useVariables(); const localVariables = useLocalVariables({ currentForm: form }); + const { templateFinshed } = useTemplateBlockContext(); const linkageRules: any[] = (getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || []; @@ -151,7 +153,7 @@ const WithForm = (props: WithFormProps) => { return result; }, getSubscriber(action, field, rule, variables, localVariables), - { fireImmediately: true }, + { fireImmediately: false }, ), ); }); @@ -166,7 +168,7 @@ const WithForm = (props: WithFormProps) => { dispose(); }); }; - }, [linkageRules]); + }, [linkageRules, templateFinshed]); return fieldSchema['x-decorator'] === 'FormV2' ? : ; }; diff --git a/packages/core/client/src/schema-component/antd/form/Form.tsx b/packages/core/client/src/schema-component/antd/form/Form.tsx index d3375b044..3f3c20469 100644 --- a/packages/core/client/src/schema-component/antd/form/Form.tsx +++ b/packages/core/client/src/schema-component/antd/form/Form.tsx @@ -101,6 +101,7 @@ export const Form: React.FC & { Designer?: any } = observer( async onSuccess(data) { await form.reset(); form.setValues(data?.data); + form.setInitialValues(data?.data); }, }, props, diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx index ecb1c0fe5..00dc5c748 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx @@ -1,9 +1,12 @@ import { FormLayout } from '@formily/antd-v5'; import { createForm } from '@formily/core'; -import { FormContext, useField } from '@formily/react'; +import { FormContext, useField, useFieldSchema } from '@formily/react'; import React, { createContext, useContext, useEffect, useMemo } from 'react'; import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider'; import useStyles from './GridCard.Decorator.style'; +import { useGridCardBlockParams } from '../../../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockParams'; +import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; + export const GridCardBlockContext = createContext({}); GridCardBlockContext.displayName = 'GridCardBlockContext'; @@ -41,24 +44,31 @@ const InternalGridCardBlockProvider = (props) => { ); }; -export const GridCardBlockProvider = (props) => { - const { params } = props; - const { filter: parsedFilter } = useParsedFilter({ - filterOption: params?.filter, - }); - const paramsWithFilter = useMemo(() => { - return { - ...params, - filter: parsedFilter, - }; - }, [parsedFilter, params]); +/** + * 用于兼容旧版 UISchema(旧版本没有 x-use-decorator-props) + * @param props + */ +const useCompatGridCardBlockParams = (props) => { + const schema = useFieldSchema(); + + // 因为 x-use-decorator-props 的值是固定的,所以可以在条件中使用 hooks + if (schema['x-use-decorator-props']) { + return props.params; + } else { + // eslint-disable-next-line react-hooks/rules-of-hooks + return useGridCardBlockParams(props); + } +}; + +export const GridCardBlockProvider = withDynamicSchemaProps((props) => { + const params = useCompatGridCardBlockParams(props); return ( - + ); -}; +}); export const useGridCardBlockContext = () => { return useContext(GridCardBlockContext); diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx index 8fdd38f58..c005d82d8 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx @@ -5,6 +5,7 @@ import { Card } from 'antd'; import React from 'react'; import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider'; import { RecordProvider } from '../../../record-provider'; +import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; const itemCss = css` display: flex; @@ -15,7 +16,7 @@ const itemCss = css` gap: 8px; `; -export const GridCardItem = (props) => { +export const GridCardItem = withDynamicSchemaProps((props) => { const field = useField(); const parentRecordData = useCollectionParentRecordData(); return ( @@ -40,4 +41,4 @@ export const GridCardItem = (props) => { ); -}; +}); diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx index 990b7763f..63104ad2d 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx @@ -11,6 +11,7 @@ import { GridCardDesigner } from './GridCard.Designer'; import { GridCardItem } from './GridCard.Item'; import { useGridCardActionBarProps } from './hooks'; import { defaultColumnCount, pageSizeOptions } from './options'; +import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; const rowGutter = { md: 12, @@ -56,7 +57,9 @@ const designerCss = css` `; const InternalGridCard = (props) => { + // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const { columnCount: columnCountProp, pagination } = useProps(props); + const { service, columnCount: _columnCount = defaultColumnCount } = useGridCardBlockContext(); const columnCount = columnCountProp || _columnCount; const { run, params } = service; @@ -145,7 +148,7 @@ const InternalGridCard = (props) => { ); }; -export const GridCard = InternalGridCard as typeof InternalGridCard & { +export const GridCard = withDynamicSchemaProps(InternalGridCard) as typeof InternalGridCard & { Item: typeof GridCardItem; Designer: typeof GridCardDesigner; Decorator: typeof GridCardBlockProvider; diff --git a/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx b/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx deleted file mode 100644 index 5652edcc2..000000000 --- a/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { render } from '@nocobase/test/client'; -import React from 'react'; -import { Pagination } from '../index'; - -describe('Pagination', () => { - it('should render correctly', () => { - render(); - expect(document.querySelector('.ant-pagination')).toBeInTheDocument(); - }); - - it('hidden', () => { - render(