Merge remote-tracking branch 'upstream/main' into @hera/dev
This commit is contained in:
commit
fd656387ea
@ -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
|
@ -29,5 +29,7 @@ services:
|
||||
MYSQL_PASSWORD: nocobase
|
||||
MYSQL_ROOT_PASSWORD: nocobase
|
||||
restart: always
|
||||
volumes:
|
||||
- ./storage/db/mysql:/var/lib/mysql
|
||||
networks:
|
||||
- nocobase
|
||||
|
@ -28,5 +28,7 @@ services:
|
||||
POSTGRES_USER: nocobase
|
||||
POSTGRES_DB: nocobase
|
||||
POSTGRES_PASSWORD: nocobase
|
||||
volumes:
|
||||
- ./storage/db/postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- nocobase
|
@ -18,6 +18,7 @@
|
||||
"scripts": {
|
||||
"nocobase": "nocobase",
|
||||
"pm": "nocobase pm",
|
||||
"pm2": "nocobase pm2",
|
||||
"dev": "nocobase dev",
|
||||
"dev-server": "nocobase dev --server",
|
||||
"start": "nocobase start",
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
66
packages/core/build/src/buildEsm.ts
Normal file
66
packages/core/build/src/buildEsm.ts
Normal file
@ -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,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
@ -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'),
|
||||
|
@ -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);
|
||||
|
28
packages/core/cli/src/commands/pm2.js
Normal file
28
packages/core/cli/src/commands/pm2.js
Normal file
@ -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']);
|
||||
});
|
||||
};
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -41,6 +41,9 @@ export class RouterManager {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getRoutesTree(): RouteObject[] {
|
||||
type RouteTypeWithChildren = RouteType & { children?: RouteTypeWithChildren };
|
||||
const routes: Record<string, RouteTypeWithChildren> = {};
|
||||
@ -106,6 +109,9 @@ export class RouterManager {
|
||||
this.options.basename = basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getRouterComponent() {
|
||||
const { type = 'browser', ...opts } = this.options;
|
||||
const Routers = {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSchemaInitializerSubMenuContext } from '../components/SchemaInitializerSubMenu';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const useAriaAttributeOfMenuItem = () => {
|
||||
const { isInMenu } = useSchemaInitializerSubMenuContext();
|
||||
// 在 Menu 中,每一项的 role 已经被标记为 menuitem 了,不需要再次标记;
|
||||
|
@ -7,6 +7,9 @@ import React from 'react';
|
||||
import { useDesignable } from '../../../schema-component';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const SchemaSettingsWrapper: FC<SchemaSettingOptions<any>> = (props) => {
|
||||
const { items, Component = SchemaSettingsIcon, name, componentProps, style, ...others } = props;
|
||||
const { dn } = useDesignable();
|
||||
|
@ -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);
|
||||
|
@ -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<string, typeof Plugin>) {
|
||||
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<Array<[string, typeof Plugin]>> {
|
||||
const { requirejs, pluginData, devDynamicImport } = options;
|
||||
if (pluginData.length === 0) return [];
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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<any>({});
|
||||
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 (
|
||||
<DetailsBlockContext.Provider value={detailsBLockValue}>
|
||||
<RecordProvider isNew={false} record={currentRecord} parent={parentRecord?.data}>
|
||||
{props.children}
|
||||
</RecordProvider>
|
||||
<div ref={formBlockRef}>
|
||||
<RecordProvider isNew={false} record={currentRecord} parent={parentRecord?.data}>
|
||||
{props.children}
|
||||
</RecordProvider>
|
||||
</div>
|
||||
</DetailsBlockContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const DetailsBlockProvider = (props) => {
|
||||
export const DetailsBlockProvider = withDynamicSchemaProps((props) => {
|
||||
return (
|
||||
<BlockProvider name="details" {...props}>
|
||||
<InternalDetailsBlockProvider {...props} />
|
||||
</BlockProvider>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
@ -72,6 +77,11 @@ export const useDetailsBlockContext = () => {
|
||||
return useContext(DetailsBlockContext);
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 即将废弃,请用 useDetailsWithPaginationProps 或者 useDetailsProps
|
||||
* @returns
|
||||
*/
|
||||
export const useDetailsBlockProps = () => {
|
||||
const ctx = useDetailsBlockContext();
|
||||
useEffect(() => {
|
||||
|
@ -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 (
|
||||
<DatePickerProvider value={{ utc: false }}>
|
||||
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
||||
@ -11,4 +12,4 @@ export const FilterFormBlockProvider = (props) => {
|
||||
</DefaultValueProvider>
|
||||
</DatePickerProvider>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -6,4 +6,5 @@ export * from './FormFieldProvider';
|
||||
export * from './TableBlockProvider';
|
||||
export * from './TableFieldProvider';
|
||||
export * from './TableSelectorProvider';
|
||||
export * from './DetailsBlockProvider';
|
||||
export * from './hooks';
|
||||
|
@ -17,7 +17,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface {
|
||||
'x-validator': 'uuid',
|
||||
},
|
||||
};
|
||||
availableTypes = ['string', 'uid'];
|
||||
availableTypes = ['string', 'uid', 'uuid'];
|
||||
properties = {
|
||||
'uiSchema.title': {
|
||||
type: 'string',
|
||||
|
@ -110,7 +110,7 @@ export class InheritanceCollectionMixin extends Collection {
|
||||
}
|
||||
|
||||
// override Collection
|
||||
getFieldsMap() {
|
||||
protected getFieldsMap() {
|
||||
if (this.fieldsMap) {
|
||||
return this.fieldsMap;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -22,7 +22,7 @@ export const CollectionFieldProvider: FC<CollectionFieldProviderProps> = (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<CollectionFieldProviderProps> = (props)
|
||||
);
|
||||
}, [collection, fieldSchema, name, collectionManager]);
|
||||
|
||||
if (!collection || allowNull) {
|
||||
if (!value && allowNull) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -131,6 +131,9 @@ export class CollectionManager {
|
||||
return this.getCollection(collectionName)?.getFields(predicate) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
clone(collections: CollectionOptions[] = []) {
|
||||
const collectionManager = new CollectionManager([], this.dataSource);
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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<CollectionDeletedPlaceholderProps> = ({ type, name, message }) => {
|
||||
const { designable, dn } = useDesignable();
|
||||
const { modal } = App.useApp();
|
||||
@ -63,7 +66,11 @@ export const CollectionDeletedPlaceholder: FC<CollectionDeletedPlaceholderProps>
|
||||
|
||||
if (designable) {
|
||||
if (type === 'Field') {
|
||||
return <Text type="secondary">{messageValue}</Text>;
|
||||
return (
|
||||
<EllipsisWithTooltip ellipsis>
|
||||
<Text type="secondary">{messageValue}</Text>
|
||||
</EllipsisWithTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -10,6 +10,9 @@ interface DataSourceApplicationProviderProps extends CollectionManagerProviderPr
|
||||
dataSourceManager: DataSourceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const DataSourceApplicationProvider: FC<DataSourceApplicationProviderProps> = ({
|
||||
children,
|
||||
dataSourceManager,
|
||||
|
@ -103,6 +103,9 @@ export interface DataBlockContextValue<T extends {} = {}> {
|
||||
export const DataBlockContext = createContext<DataBlockContextValue<any>>({} as any);
|
||||
DataBlockContext.displayName = 'DataBlockContext';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const AssociationOrCollectionProvider = (props: {
|
||||
collection: string | CollectionOptions;
|
||||
association: string;
|
||||
|
@ -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<UseRequestResult<any>>(null);
|
||||
BlockRequestContext.displayName = 'BlockRequestContext';
|
||||
@ -55,6 +56,7 @@ function useCurrentRequest<T>(options: Omit<AllDataBlockProps, 'type'>) {
|
||||
function useParentRequest<T>(options: Omit<AllDataBlockProps, 'type'>) {
|
||||
const { sourceId, association, parentRecord } = options;
|
||||
const api = useAPIClient();
|
||||
const cm = useCollectionManager();
|
||||
const dataBlockProps = useDataBlockProps();
|
||||
const headers = useDataSourceHeaders(dataBlockProps.dataSource);
|
||||
return useRequest<T>(
|
||||
@ -63,8 +65,12 @@ function useParentRequest<T>(options: Omit<AllDataBlockProps, 'type'>) {
|
||||
if (!association) return Promise.resolve({ data: undefined });
|
||||
// "association": "Collection.Field"
|
||||
const arr = association.split('.');
|
||||
// <collection>:get/<filterByTk>
|
||||
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;
|
||||
// <collection>: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,
|
||||
|
@ -83,6 +83,9 @@ export abstract class DataSource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class LocalDataSource extends DataSource {
|
||||
getDataSource() {
|
||||
return {
|
||||
|
@ -53,6 +53,10 @@ export const detailsActionInitializers_deprecated = new CompatibleSchemaInitiali
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 已弃用,请使用 readPrettyFormActionInitializers 代替
|
||||
*/
|
||||
export const detailsActionInitializers = new CompatibleSchemaInitializer(
|
||||
{
|
||||
name: 'detailsWithPaging:configureActions',
|
||||
|
@ -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);
|
||||
}}
|
||||
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
@ -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,
|
||||
});
|
@ -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,
|
||||
};
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { useDetailsBlockProps } from '../../../../../block-provider/DetailsBlockProvider';
|
||||
|
||||
export function useDetailsWithPaginationProps() {
|
||||
return useDetailsBlockProps();
|
||||
}
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -47,6 +47,7 @@ const AssociatedFields = () => {
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 已弃用,请使用 readPrettyFormItemInitializers 代替
|
||||
*/
|
||||
export const readPrettyFormItemInitializers_deprecated = new CompatibleSchemaInitializer({
|
||||
name: 'ReadPrettyFormItemInitializers',
|
||||
|
@ -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 };
|
||||
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -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: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
@ -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,
|
||||
});
|
@ -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,
|
||||
};
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { useDetailsBlockProps } from '../../../../../block-provider/DetailsBlockProvider';
|
||||
|
||||
export function useDetailsProps() {
|
||||
return useDetailsBlockProps();
|
||||
}
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -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 }) => {
|
||||
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
@ -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);
|
||||
}}
|
||||
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
@ -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,
|
||||
};
|
||||
}
|
@ -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;
|
||||
}
|
@ -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',
|
||||
}),
|
||||
);
|
||||
}}
|
||||
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -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;
|
||||
};
|
@ -0,0 +1 @@
|
||||
export function useFilterFormBlockDecoratorProps() {}
|
@ -0,0 +1,5 @@
|
||||
import { useFormBlockProps } from '../../../../../block-provider/FormBlockProvider';
|
||||
|
||||
export function useFilterFormBlockProps() {
|
||||
return useFormBlockProps();
|
||||
}
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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 = {
|
||||
|
@ -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',
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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<Field>();
|
||||
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'),
|
||||
|
@ -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;
|
||||
}
|
@ -33,7 +33,7 @@ export const PoweredBy = () => {
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: parseHTML(
|
||||
customBrandPlugin?.options?.options?.brand ||
|
||||
`Powered by <a href="${urls[i18n.language] || urls['en-US']}">NocoBase</a>`,
|
||||
`Powered by <a href="${urls[i18n.language] || urls['en-US']}" target="__blank">NocoBase</a>`,
|
||||
{ appVersion },
|
||||
),
|
||||
}}
|
||||
|
@ -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<any> = (props) => {
|
||||
export const BlockItem: React.FC<any> = 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<any> = (props) => {
|
||||
{children}
|
||||
</SortableItem>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -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 <FormV2 {...props} />;
|
||||
};
|
||||
});
|
||||
|
@ -67,16 +67,15 @@ export const FormItem: any = observer(
|
||||
},
|
||||
);
|
||||
}, [showTitle]);
|
||||
const fieldSchema = useFieldSchema();
|
||||
|
||||
return (
|
||||
<BlockItem className={'nb-form-item'}>
|
||||
<CollectionFieldProvider name={fieldSchema.name} allowNull={!fieldSchema['x-collection-field']}>
|
||||
<CollectionFieldProvider allowNull={true}>
|
||||
<BlockItem className={'nb-form-item'}>
|
||||
<ACLCollectionFieldProvider>
|
||||
<Item className={className} {...props} extra={extra} />
|
||||
</ACLCollectionFieldProvider>
|
||||
</CollectionFieldProvider>
|
||||
</BlockItem>
|
||||
</BlockItem>
|
||||
</CollectionFieldProvider>
|
||||
);
|
||||
},
|
||||
{ displayName: 'FormItem' },
|
||||
|
@ -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' ? <FormDecorator {...props} /> : <FormComponent {...props} />;
|
||||
};
|
||||
|
@ -101,6 +101,7 @@ export const Form: React.FC<FormProps> & { Designer?: any } = observer(
|
||||
async onSuccess(data) {
|
||||
await form.reset();
|
||||
form.setValues(data?.data);
|
||||
form.setInitialValues(data?.data);
|
||||
},
|
||||
},
|
||||
props,
|
||||
|
@ -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<any>({});
|
||||
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 (
|
||||
<BlockProvider name="grid-card" {...props} params={paramsWithFilter}>
|
||||
<BlockProvider name="grid-card" {...props} params={params}>
|
||||
<InternalGridCardBlockProvider {...props} />
|
||||
</BlockProvider>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export const useGridCardBlockContext = () => {
|
||||
return useContext(GridCardBlockContext);
|
||||
|
@ -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<ObjectField>();
|
||||
const parentRecordData = useCollectionParentRecordData();
|
||||
return (
|
||||
@ -40,4 +41,4 @@ export const GridCardItem = (props) => {
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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(<Pagination />);
|
||||
expect(document.querySelector('.ant-pagination')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('hidden', () => {
|
||||
render(<Pagination hidden />);
|
||||
expect(document.querySelector('.ant-pagination')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -2,24 +2,29 @@ import { observer } from '@formily/react';
|
||||
import { Pagination as AntdPagination } from 'antd';
|
||||
import React, { KeyboardEventHandler } from 'react';
|
||||
import { useProps } from '../../hooks/useProps';
|
||||
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
|
||||
|
||||
export const Pagination = observer(
|
||||
(props: any) => {
|
||||
const { hidden, ...others } = useProps(props);
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
const onKeypress: KeyboardEventHandler<HTMLDivElement> = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
export const Pagination = withDynamicSchemaProps(
|
||||
observer(
|
||||
(props: any) => {
|
||||
// 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { hidden, ...others } = useProps(props);
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const onKeypress: KeyboardEventHandler<HTMLDivElement> = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div onKeyPress={onKeypress}>
|
||||
<AntdPagination {...others} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Pagination' },
|
||||
return (
|
||||
<div onKeyPress={onKeypress}>
|
||||
<AntdPagination {...others} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Pagination' },
|
||||
),
|
||||
);
|
||||
|
@ -52,7 +52,8 @@ const InternalRemoteSelect = connect(
|
||||
const { getField } = useCollection_deprecated();
|
||||
const searchData = useRef(null);
|
||||
const { getCollectionJoinField, getInterface } = useCollectionManager_deprecated();
|
||||
const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema.name as string);
|
||||
const colletionFieldName = fieldSchema['x-collection-field'] || fieldSchema.name;
|
||||
const collectionField = getField(colletionFieldName) || getCollectionJoinField(colletionFieldName);
|
||||
const targetField =
|
||||
_targetField ||
|
||||
(collectionField?.target &&
|
||||
|
@ -123,7 +123,6 @@ export * from './buttons';
|
||||
export * from './items';
|
||||
export {
|
||||
createDetailsBlockSchema,
|
||||
createFilterFormBlockSchema,
|
||||
createFormBlockSchema,
|
||||
createReadPrettyFormBlockSchema,
|
||||
createTableBlockSchema,
|
||||
|
@ -4,7 +4,8 @@ import React, { useCallback } from 'react';
|
||||
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
||||
import { useCollectionManager_deprecated } from '../../collection-manager';
|
||||
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||
import { createDetailsBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
|
||||
import { useRecordCollectionDataSourceItems } from '../utils';
|
||||
import { createDetailsWithPaginationUISchema } from '../../modules/blocks/data-blocks/details-multi/createDetailsWithPaginationUISchema';
|
||||
|
||||
export const RecordAssociationDetailsBlockInitializer = () => {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
@ -25,13 +26,10 @@ export const RecordAssociationDetailsBlockInitializer = () => {
|
||||
insert(s);
|
||||
} else {
|
||||
insert(
|
||||
createDetailsBlockSchema({
|
||||
collection: field.target,
|
||||
resource,
|
||||
createDetailsWithPaginationUISchema({
|
||||
dataSource: collection.dataSource,
|
||||
association: resource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
settings: 'blockSettings:multiDataDetails',
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -51,12 +49,10 @@ export function useCreateAssociationDetailsBlock() {
|
||||
const collection = getCollection(field.target);
|
||||
|
||||
insert(
|
||||
createDetailsBlockSchema({
|
||||
collection: field.target,
|
||||
createDetailsWithPaginationUISchema({
|
||||
dataSource: collection.dataSource,
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
settings: 'blockSettings:multiDataDetails',
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
@ -4,7 +4,8 @@ import React, { useCallback } from 'react';
|
||||
import { useCollectionManager_deprecated } from '../../collection-manager';
|
||||
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
||||
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||
import { createGridCardBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
|
||||
import { useRecordCollectionDataSourceItems } from '../utils';
|
||||
import { createGridCardBlockSchema } from '../../modules/blocks/data-blocks/grid-card/createGridCardBlockSchema';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
@ -31,11 +32,8 @@ export const RecordAssociationGridCardBlockInitializer = () => {
|
||||
insert(
|
||||
createGridCardBlockSchema({
|
||||
rowKey: collection.filterTargetKey,
|
||||
collection: field.target,
|
||||
resource,
|
||||
dataSource: collection.dataSource,
|
||||
association: resource,
|
||||
settings: 'blockSettings:gridCard',
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -57,10 +55,8 @@ export function useCreateAssociationGridCardBlock() {
|
||||
insert(
|
||||
createGridCardBlockSchema({
|
||||
rowKey: collection.filterTargetKey,
|
||||
collection: field.target,
|
||||
dataSource: collection.dataSource,
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
settings: 'blockSettings:gridCard',
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
@ -2,10 +2,10 @@ import { FormOutlined } from '@ant-design/icons';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
|
||||
import { useBlockRequestContext } from '../../block-provider';
|
||||
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||
import { createDetailsBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
|
||||
import { useRecordCollectionDataSourceItems } from '../utils';
|
||||
import { useCollectionManager_deprecated } from '../../collection-manager';
|
||||
import { createDetailsUISchema } from '../../modules/blocks/data-blocks/details-single/createDetailsUISchema';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
@ -22,8 +22,6 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
|
||||
const collection = getCollection(collectionName);
|
||||
|
||||
const resource = `${field.collectionName}.${field.name}`;
|
||||
const { block } = useBlockRequestContext();
|
||||
const actionInitializers = block !== 'TableField' ? 'details:configureActions' : null;
|
||||
|
||||
return (
|
||||
<SchemaInitializerItem
|
||||
@ -33,17 +31,10 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
if (item.template.componentName === 'ReadPrettyFormItem') {
|
||||
const blockSchema = createDetailsBlockSchema({
|
||||
actionInitializers,
|
||||
collection: collectionName,
|
||||
const blockSchema = createDetailsUISchema({
|
||||
dataSource: collection.dataSource,
|
||||
resource,
|
||||
association: resource,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
template: s,
|
||||
settings: 'blockSettings:singleDataDetails',
|
||||
templateSchema: s,
|
||||
});
|
||||
if (item.mode === 'reference') {
|
||||
blockSchema['x-template-key'] = item.template.key;
|
||||
@ -54,16 +45,9 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
|
||||
}
|
||||
} else {
|
||||
insert(
|
||||
createDetailsBlockSchema({
|
||||
actionInitializers,
|
||||
collection: collectionName,
|
||||
resource,
|
||||
createDetailsUISchema({
|
||||
association: resource,
|
||||
dataSource: collection.dataSource,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
settings: 'blockSettings:singleDataDetails',
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -76,8 +60,6 @@ export const RecordReadPrettyAssociationFormBlockInitializer = () => {
|
||||
export function useCreateAssociationDetailsWithoutPagination() {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { getCollection } = useCollectionManager_deprecated();
|
||||
const { block } = useBlockRequestContext();
|
||||
const actionInitializers = block !== 'TableField' ? 'details:configureActions' : null;
|
||||
|
||||
const createAssociationDetailsWithoutPagination = useCallback(
|
||||
({ item }) => {
|
||||
@ -85,19 +67,13 @@ export function useCreateAssociationDetailsWithoutPagination() {
|
||||
const collection = getCollection(field.target);
|
||||
|
||||
insert(
|
||||
createDetailsBlockSchema({
|
||||
actionInitializers,
|
||||
collection: field.target,
|
||||
createDetailsUISchema({
|
||||
dataSource: collection.dataSource,
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
settings: 'blockSettings:singleDataDetails',
|
||||
}),
|
||||
);
|
||||
},
|
||||
[actionInitializers, getCollection, insert],
|
||||
[getCollection, insert],
|
||||
);
|
||||
|
||||
const templateWrap = useCallback(
|
||||
@ -106,16 +82,10 @@ export function useCreateAssociationDetailsWithoutPagination() {
|
||||
const collection = getCollection(field.target);
|
||||
|
||||
if (item.template.componentName === 'ReadPrettyFormItem') {
|
||||
const blockSchema = createDetailsBlockSchema({
|
||||
actionInitializers,
|
||||
collection: field.target,
|
||||
const blockSchema = createDetailsUISchema({
|
||||
dataSource: collection.dataSource,
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
template: templateSchema,
|
||||
settings: 'blockSettings:singleDataDetails',
|
||||
templateSchema: templateSchema,
|
||||
});
|
||||
if (item.mode === 'reference') {
|
||||
blockSchema['x-template-key'] = item.template.key;
|
||||
@ -125,7 +95,7 @@ export function useCreateAssociationDetailsWithoutPagination() {
|
||||
return templateSchema;
|
||||
}
|
||||
},
|
||||
[actionInitializers, getCollection],
|
||||
[getCollection],
|
||||
);
|
||||
|
||||
return { createAssociationDetailsWithoutPagination, templateWrap };
|
||||
|
@ -893,7 +893,18 @@ export const useCollectionDataSourceItems = ({
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createDetailsBlockSchema = (options) => {
|
||||
export const createDetailsBlockSchema = (options: {
|
||||
collection: string;
|
||||
dataSource: string;
|
||||
rowKey?: string;
|
||||
formItemInitializers?: string;
|
||||
actionInitializers?: string;
|
||||
association?: string;
|
||||
template?: any;
|
||||
settings?: string;
|
||||
action?: string;
|
||||
[key: string]: any;
|
||||
}) => {
|
||||
const {
|
||||
formItemInitializers = 'details:configureFields',
|
||||
actionInitializers = 'detailsWithPaging:configureActions',
|
||||
@ -972,97 +983,6 @@ export const createDetailsBlockSchema = (options) => {
|
||||
return schema;
|
||||
};
|
||||
|
||||
export const createGridCardBlockSchema = (options) => {
|
||||
const {
|
||||
formItemInitializers = 'details:configureFields',
|
||||
actionInitializers = 'gridCard:configureActions',
|
||||
itemActionInitializers = 'gridCard:configureItemActions',
|
||||
collection,
|
||||
association,
|
||||
template,
|
||||
dataSource,
|
||||
settings,
|
||||
...others
|
||||
} = options;
|
||||
const resourceName = association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${resourceName}:view`,
|
||||
'x-decorator': 'GridCard.Decorator',
|
||||
'x-decorator-props': {
|
||||
collection,
|
||||
association,
|
||||
dataSource,
|
||||
readPretty: true,
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 12,
|
||||
},
|
||||
runWhenParamsChanged: true,
|
||||
...others,
|
||||
},
|
||||
'x-component': 'BlockItem',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardBlockItemProps }}',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': settings,
|
||||
properties: {
|
||||
actionBar: {
|
||||
type: 'void',
|
||||
'x-initializer': actionInitializers,
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 'var(--nb-spacing)',
|
||||
},
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
list: {
|
||||
type: 'array',
|
||||
'x-component': 'GridCard',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
item: {
|
||||
type: 'object',
|
||||
'x-component': 'GridCard.Item',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardItemProps }}',
|
||||
},
|
||||
properties: {
|
||||
grid: template || {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': formItemInitializers,
|
||||
'x-initializer-props': {
|
||||
useProps: '{{ useGridCardItemInitializerProps }}',
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
actionBar: {
|
||||
type: 'void',
|
||||
'x-align': 'left',
|
||||
'x-initializer': itemActionInitializers,
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardActionBarProps }}',
|
||||
layout: 'one-column',
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return schema;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 已弃用,请使用 createCreateFormBlockUISchema 或者 createEditFormBlockUISchema 替代
|
||||
@ -1153,71 +1073,6 @@ export const createFormBlockSchema = (options: {
|
||||
return schema;
|
||||
};
|
||||
|
||||
export const createFilterFormBlockSchema = (options) => {
|
||||
const {
|
||||
formItemInitializers = 'filterForm:configureFields',
|
||||
actionInitializers = 'filterForm:configureActions',
|
||||
collection,
|
||||
resource,
|
||||
association,
|
||||
dataSource,
|
||||
action,
|
||||
template,
|
||||
settings,
|
||||
...others
|
||||
} = options;
|
||||
const resourceName = resource || association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-decorator': 'FilterFormBlockProvider',
|
||||
'x-decorator-props': {
|
||||
...others,
|
||||
action,
|
||||
resource: resourceName,
|
||||
dataSource,
|
||||
collection,
|
||||
association,
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
...(settings ? { 'x-settings': settings } : { 'x-designer': 'FormV2.FilterDesigner' }),
|
||||
'x-component': 'CardItem',
|
||||
// 保存当前筛选区块所能过滤的数据区块
|
||||
'x-filter-targets': [],
|
||||
// 用于存储用户设置的每个字段的运算符,目前仅筛选表单区块支持自定义
|
||||
'x-filter-operators': {},
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
grid: template || {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': formItemInitializers,
|
||||
properties: {},
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-initializer': actionInitializers,
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
layout: 'one-column',
|
||||
style: {
|
||||
float: 'right',
|
||||
},
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return schema;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* 已弃用,可以使用 createDetailsBlockSchema 替换
|
||||
|
@ -42,11 +42,11 @@ import {
|
||||
ActionContextProvider,
|
||||
AssociationOrCollectionProvider,
|
||||
CollectionFieldOptions_deprecated,
|
||||
CollectionRecordProvider,
|
||||
DataSourceApplicationProvider,
|
||||
Designable,
|
||||
FormDialog,
|
||||
FormProvider,
|
||||
CollectionRecordProvider,
|
||||
RemoteSchemaComponent,
|
||||
SchemaComponent,
|
||||
SchemaComponentContext,
|
||||
@ -56,6 +56,7 @@ import {
|
||||
useAPIClient,
|
||||
useBlockRequestContext,
|
||||
useCollectionManager_deprecated,
|
||||
useCollectionRecord,
|
||||
useCollection_deprecated,
|
||||
useCompile,
|
||||
useDataBlockProps,
|
||||
@ -63,7 +64,6 @@ import {
|
||||
useFilterBlock,
|
||||
useGlobalTheme,
|
||||
useLinkageCollectionFilterOptions,
|
||||
useCollectionRecord,
|
||||
useRecord,
|
||||
useSchemaSettingsItem,
|
||||
useSortFields,
|
||||
@ -84,9 +84,9 @@ import {
|
||||
useFormActiveFields,
|
||||
} from '../block-provider/hooks';
|
||||
import { SelectWithTitle, SelectWithTitleProps } from '../common/SelectWithTitle';
|
||||
import { useNiceDropdownMaxHeight } from '../common/useNiceDropdownHeight';
|
||||
import { useDataSourceManager } from '../data-source/data-source/DataSourceManagerProvider';
|
||||
import { useDataSourceKey } from '../data-source/data-source/DataSourceProvider';
|
||||
import { useNiceDropdownMaxHeight } from '../common/useNiceDropdownHeight';
|
||||
import {
|
||||
FilterBlockType,
|
||||
getSupportFieldsByAssociation,
|
||||
@ -574,7 +574,7 @@ export const SchemaSettingsRemove: FC<SchemaSettingsRemoveProps> = (props) => {
|
||||
field.required = false;
|
||||
fieldSchema['required'] = false;
|
||||
}
|
||||
if (template && ctx?.dn) {
|
||||
if (template && template.uid === fieldSchema['x-uid'] && ctx?.dn) {
|
||||
await ctx?.dn.remove(null, options);
|
||||
} else {
|
||||
await dn.remove(null, options);
|
||||
|
@ -15,8 +15,14 @@ import { customizeUpdateRecordActionSettings } from '../modules/actions/update-r
|
||||
import { customizePopupActionSettings } from '../modules/actions/view-edit-popup/customizePopupActionSettings';
|
||||
import { editActionSettings } from '../modules/actions/view-edit-popup/editActionSettings';
|
||||
import { viewActionSettings } from '../modules/actions/view-edit-popup/viewActionSettings';
|
||||
import { multiDataDetailsBlockSettings } from '../modules/blocks/data-blocks/details-multi/multiDataDetailsBlockSettings';
|
||||
import { singleDataDetailsBlockSettings } from '../modules/blocks/data-blocks/details-single/singleDataDetailsBlockSettings';
|
||||
import {
|
||||
detailsWithPaginationSettings,
|
||||
multiDataDetailsBlockSettings,
|
||||
} from '../modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings';
|
||||
import {
|
||||
detailsBlockSettings,
|
||||
singleDataDetailsBlockSettings,
|
||||
} from '../modules/blocks/data-blocks/details-single/detailsBlockSettings';
|
||||
import {
|
||||
createFormBlockSettings,
|
||||
creationFormBlockSettings,
|
||||
@ -57,7 +63,9 @@ export class SchemaSettingsPlugin extends Plugin {
|
||||
this.schemaSettingsManager.add(filterFormBlockSettings);
|
||||
this.schemaSettingsManager.add(filterFormItemFieldSettings);
|
||||
this.schemaSettingsManager.add(multiDataDetailsBlockSettings);
|
||||
this.schemaSettingsManager.add(detailsWithPaginationSettings);
|
||||
this.schemaSettingsManager.add(singleDataDetailsBlockSettings);
|
||||
this.schemaSettingsManager.add(detailsBlockSettings);
|
||||
this.schemaSettingsManager.add(tableSelectorBlockSettings);
|
||||
this.schemaSettingsManager.add(listBlockSettings);
|
||||
this.schemaSettingsManager.add(gridCardBlockSettings);
|
||||
|
@ -111,6 +111,8 @@ export const Help = observer(
|
||||
<Popover
|
||||
// nb-about 的样式定义在 plugin-custom-brand 插件中
|
||||
rootClassName="nb-about"
|
||||
placement="bottomRight"
|
||||
arrow={false}
|
||||
content={<div dangerouslySetInnerHTML={{ __html: content }}></div>}
|
||||
>
|
||||
{icon}
|
||||
|
@ -1,3 +1,3 @@
|
||||
import { defineConfig } from '@nocobase/test';
|
||||
import { defineConfig } from '@nocobase/test/vitest.mjs';
|
||||
|
||||
export default defineConfig();
|
||||
|
@ -29,12 +29,12 @@ const availableActions: {
|
||||
update: {
|
||||
displayName: '{{t("Edit")}}',
|
||||
type: 'old-data',
|
||||
aliases: ['update', 'move'],
|
||||
aliases: ['update', 'move', 'add', 'set', 'remove', 'toggle'],
|
||||
allowConfigureFields: true,
|
||||
},
|
||||
destroy: {
|
||||
displayName: '{{t("Delete")}}',
|
||||
aliases: ['destroy', 'remove'],
|
||||
aliases: ['destroy'],
|
||||
type: 'old-data',
|
||||
},
|
||||
};
|
||||
|
@ -357,19 +357,25 @@ export class PluginManager {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
let sourceDir = basename(dirname(__dirname)) === 'src' ? 'src' : 'dist';
|
||||
const packageNames: string[] = items.map((item) => item.packageName);
|
||||
const source = [];
|
||||
for (const packageName of packageNames) {
|
||||
const directory = join(packageName, sourceDir, 'server/commands/*.' + (sourceDir === 'src' ? 'ts' : 'js'));
|
||||
source.push(directory);
|
||||
const file = require.resolve(packageName);
|
||||
const sourceDir = basename(dirname(file)) === 'src' ? 'src' : 'dist';
|
||||
const directory = join(
|
||||
packageName,
|
||||
sourceDir,
|
||||
'server/commands/*.' + (basename(dirname(file)) === 'src' ? 'ts' : 'js'),
|
||||
);
|
||||
source.push(directory.replaceAll(sep, '/'));
|
||||
}
|
||||
sourceDir = basename(dirname(__dirname)) === 'src' ? 'src' : 'lib';
|
||||
for (const plugin of this.options.plugins || []) {
|
||||
if (typeof plugin === 'string') {
|
||||
const packageName = await PluginManager.getPackageName(plugin);
|
||||
const file = require.resolve(packageName);
|
||||
const sourceDir = basename(dirname(file)) === 'src' ? 'src' : 'lib';
|
||||
const directory = join(packageName, sourceDir, 'server/commands/*.' + (sourceDir === 'src' ? 'ts' : 'js'));
|
||||
source.push(directory);
|
||||
source.push(directory.replaceAll(sep, '/'));
|
||||
}
|
||||
}
|
||||
const files = await fg(source, {
|
||||
|
@ -12,8 +12,8 @@
|
||||
"default": "./lib/index.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./vitest.d.ts",
|
||||
"default": "./vitest.mjs"
|
||||
"types": "./es/index.d.ts",
|
||||
"default": "./es/index.mjs"
|
||||
}
|
||||
},
|
||||
"./client": {
|
||||
@ -22,8 +22,8 @@
|
||||
"default": "./lib/client/index.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./lib/client/index.d.ts",
|
||||
"default": "./lib/client/index.js"
|
||||
"types": "./es/client/index.d.ts",
|
||||
"default": "./es/client/index.mjs"
|
||||
}
|
||||
},
|
||||
"./e2e": {
|
||||
@ -32,11 +32,12 @@
|
||||
"default": "./lib/e2e/index.js"
|
||||
},
|
||||
"import": {
|
||||
"types": "./lib/e2e/index.d.ts",
|
||||
"default": "./lib/e2e/index.js"
|
||||
"types": "./es/e2e/index.d.ts",
|
||||
"default": "./es/e2e/index.mjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
"./package.json": "./package.json",
|
||||
"./vitest.mjs": "./vitest.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "8.1.0",
|
||||
@ -45,6 +46,7 @@
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"jsdom": "^16.0.0",
|
||||
@ -58,7 +60,6 @@
|
||||
"supertest": "^6.1.6",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^1.4.0",
|
||||
"vitest-dom": "^0.1.1",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
import { configure } from '@testing-library/react';
|
||||
import dotenv from 'dotenv';
|
||||
import { vi } from 'vitest';
|
||||
import 'vitest-dom/extend-expect';
|
||||
|
||||
/**
|
||||
* 解决 TypeError: URL.createObjectURL is not a function
|
||||
|
22
packages/core/test/src/defineConfig.d.ts
vendored
22
packages/core/test/src/defineConfig.d.ts
vendored
@ -1,22 +0,0 @@
|
||||
declare global {
|
||||
const suite: (typeof import('vitest'))['suite'];
|
||||
const test: (typeof import('vitest'))['test'];
|
||||
const describe: (typeof import('vitest'))['describe'];
|
||||
const it: (typeof import('vitest'))['it'];
|
||||
const expectTypeOf: (typeof import('vitest'))['expectTypeOf'];
|
||||
const assertType: (typeof import('vitest'))['assertType'];
|
||||
const expect: (typeof import('vitest'))['expect'];
|
||||
const assert: (typeof import('vitest'))['assert'];
|
||||
const vitest: (typeof import('vitest'))['vitest'];
|
||||
const vi: (typeof import('vitest'))['vitest'];
|
||||
const beforeAll: (typeof import('vitest'))['beforeAll'];
|
||||
const afterAll: (typeof import('vitest'))['afterAll'];
|
||||
const beforeEach: (typeof import('vitest'))['beforeEach'];
|
||||
const afterEach: (typeof import('vitest'))['afterEach'];
|
||||
}
|
||||
import { type UserConfig } from 'vitest/config';
|
||||
export declare const defineConfig: (
|
||||
config?: UserConfig & {
|
||||
server: boolean;
|
||||
},
|
||||
) => UserConfig;
|
@ -1,2 +0,0 @@
|
||||
// @ts-ignore
|
||||
export * from '../vitest';
|
@ -53,7 +53,6 @@ export interface CollectionSetting {
|
||||
* @default false
|
||||
*/
|
||||
inherit?: boolean;
|
||||
inherits?: string[];
|
||||
category?: any[];
|
||||
hidden?: boolean;
|
||||
description?: string;
|
||||
@ -120,6 +119,23 @@ interface AclRoleSetting {
|
||||
key?: string;
|
||||
//菜单权限配置
|
||||
menuUiSchemas?: string[];
|
||||
dataSourceKey?: string;
|
||||
}
|
||||
|
||||
interface DatabaseSetting {
|
||||
database: string;
|
||||
host: string;
|
||||
port: string;
|
||||
schema?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
interface DataSourceSetting {
|
||||
key: string;
|
||||
displayName: string;
|
||||
type: string;
|
||||
options: DatabaseSetting;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface PageConfig {
|
||||
@ -229,6 +245,15 @@ interface ExtendUtils {
|
||||
* 更新角色权限配置
|
||||
*/
|
||||
updateRole: <T = any>(roleSetting: AclRoleSetting) => Promise<T>;
|
||||
/**
|
||||
* 创建一个外部数据源(pg)
|
||||
*/
|
||||
mockExternalDataSource: <T = any>(DataSourceSetting: DataSourceSetting) => Promise<T>;
|
||||
/**
|
||||
* 删除外部数据源
|
||||
* @param key 外部数据源key
|
||||
*/
|
||||
destoryExternalDataSource: <T = any>(key: string) => Promise<T>;
|
||||
}
|
||||
|
||||
const PORT = process.env.APP_PORT || 20000;
|
||||
@ -437,6 +462,20 @@ const _test = base.extend<ExtendUtils>({
|
||||
|
||||
await use(updateRole);
|
||||
},
|
||||
mockExternalDataSource: async ({ browser }, use) => {
|
||||
const mockExternalDataSource = async (DataSourceSetting: DataSourceSetting) => {
|
||||
return createExternalDataSource(DataSourceSetting);
|
||||
};
|
||||
|
||||
await use(mockExternalDataSource);
|
||||
},
|
||||
destoryExternalDataSource: async ({ browser }, use) => {
|
||||
const destoryDataSource = async (key: string) => {
|
||||
return destoryExternalDataSource(key);
|
||||
};
|
||||
|
||||
await use(destoryDataSource);
|
||||
},
|
||||
});
|
||||
|
||||
export const test = Object.assign(_test, {
|
||||
@ -581,10 +620,6 @@ const deleteCollections = async (collectionNames: string[]) => {
|
||||
|
||||
const result = await api.post(`/api/collections:destroy?${params}`, {
|
||||
headers,
|
||||
params: {
|
||||
// 自动删除依赖于集合的对象(如视图),进而删除依赖于这些对象的所有对象
|
||||
cascade: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.ok()) {
|
||||
@ -696,8 +731,12 @@ const updateRole = async (roleSetting: AclRoleSetting) => {
|
||||
const state = await api.storageState();
|
||||
const headers = getHeaders(state);
|
||||
const name = roleSetting.name;
|
||||
const dataSourceKey = roleSetting.dataSourceKey;
|
||||
|
||||
const result = await api.post(`/api/roles:update?filterByTk=${name}`, {
|
||||
const url = !dataSourceKey
|
||||
? `/api/roles:update?filterByTk=${name}`
|
||||
: `/api/dataSources/${dataSourceKey}/roles:update?filterByTk=${name}`;
|
||||
const result = await api.post(url, {
|
||||
headers,
|
||||
data: { ...roleSetting },
|
||||
});
|
||||
@ -724,7 +763,48 @@ const setDefaultRole = async (name) => {
|
||||
data: { roleName: name },
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 创建外部数据源
|
||||
* @paramn
|
||||
*/
|
||||
const createExternalDataSource = async (dataSourceSetting: DataSourceSetting) => {
|
||||
const api = await request.newContext({
|
||||
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
|
||||
});
|
||||
const state = await api.storageState();
|
||||
const headers = getHeaders(state);
|
||||
const result = await api.post(`/api/dataSources:create`, {
|
||||
headers,
|
||||
data: { ...dataSourceSetting },
|
||||
});
|
||||
|
||||
if (!result.ok()) {
|
||||
throw new Error(await result.text());
|
||||
}
|
||||
const dataSourceData = (await result.json()).data;
|
||||
return dataSourceData;
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除外部数据源
|
||||
* @paramn
|
||||
*/
|
||||
const destoryExternalDataSource = async (key) => {
|
||||
const api = await request.newContext({
|
||||
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
|
||||
});
|
||||
const state = await api.storageState();
|
||||
const headers = getHeaders(state);
|
||||
const result = await api.post(`/api/dataSources:destroy?filterByTk=${key}`, {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!result.ok()) {
|
||||
throw new Error(await result.text());
|
||||
}
|
||||
const dataSourceData = (await result.json()).data;
|
||||
return dataSourceData;
|
||||
};
|
||||
/**
|
||||
* 根据 collection 的配置生成 Faker 数据
|
||||
* @param collectionSetting
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user