feat: tree block (#4566)

* feat: tree block

* fix: remove showSettings

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: table tree expand

* fix: bug

* fix: connection bug T-4501

* fix: template settings bug
This commit is contained in:
jack zhang 2024-06-11 22:53:04 +08:00 committed by GitHub
parent 868813b5fc
commit a5fc22c6b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 10089 additions and 441 deletions

View File

@ -110,11 +110,11 @@ const mocks = {
},
],
},
[`${collection}:get/1`]: {
[`${collection}:get?filter[id]=1`]: {
id: 1,
username: 'Tom',
},
[`${collection}:get/2`]: {
[`${collection}:get?filter[id]=2`]: {
id: 1,
username: 'Jack',
},

View File

@ -12,3 +12,4 @@ export * from './SchemaSettingsManager';
export * from './components';
export * from './context/SchemaSettingItemContext';
export * from './types';
export * from './utils';

View File

@ -0,0 +1,66 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { SchemaSettingsItemType, useCompile, useDesignable } from '@nocobase/client';
import { ISchema, useFieldSchema } from '@formily/react';
import _ from 'lodash';
import { TFunction, useTranslation } from 'react-i18next';
import { getNewSchema, useHookDefault } from './util';
export interface CreateModalSchemaSettingsItemProps {
name: string;
title: string | ((t: TFunction<'translation', undefined>) => string);
parentSchemaKey: string;
defaultValue?: any;
useDefaultValue?: () => any;
schema: (defaultValue: any) => ISchema;
valueKeys?: string[];
useVisible?: () => boolean;
}
/**
* create `switch` type schema settings item
*
* @internal
* @unstable
*/
export function createModalSettingsItem(options: CreateModalSchemaSettingsItemProps): SchemaSettingsItemType {
const {
name,
parentSchemaKey,
valueKeys,
schema,
title,
useVisible,
defaultValue: propsDefaultValue,
useDefaultValue = useHookDefault,
} = options;
return {
name,
type: 'actionModal',
useComponentProps() {
const fieldSchema = useFieldSchema();
const { deepMerge } = useDesignable();
const defaultValue = useDefaultValue(propsDefaultValue);
const values = _.get(fieldSchema, parentSchemaKey);
const compile = useCompile();
const { t } = useTranslation();
return {
title: typeof title === 'function' ? title(t) : compile(title),
useVisible,
schema: schema({ ...defaultValue, ...values }),
onSubmit(values) {
deepMerge(getNewSchema({ fieldSchema, schemaKey: parentSchemaKey, value: values, valueKeys }));
},
};
},
};
}

View File

@ -0,0 +1,68 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import _ from 'lodash';
import { useFieldSchema } from '@formily/react';
import { SchemaSettingsItemType, SelectProps, useCompile, useDesignable } from '@nocobase/client';
import { getNewSchema, useHookDefault } from './util';
import { TFunction, useTranslation } from 'react-i18next';
interface CreateSelectSchemaSettingsItemProps {
name: string;
title: string | ((t: TFunction<'translation', undefined>) => string);
options?: SelectProps['options'];
useOptions?: () => SelectProps['options'];
schemaKey: string;
defaultValue?: string | number;
useDefaultValue?: () => string | number;
useVisible?: () => boolean;
}
/**
* create `select` type schema settings item
*
* @internal
* @unstable
*/
export const createSelectSchemaSettingsItem = (
options: CreateSelectSchemaSettingsItemProps,
): SchemaSettingsItemType => {
const {
name,
title,
options: propsOptions,
useOptions = useHookDefault,
schemaKey,
useVisible,
defaultValue: propsDefaultValue,
useDefaultValue = useHookDefault,
} = options;
return {
name,
type: 'select',
useComponentProps() {
const filedSchema = useFieldSchema();
const { deepMerge } = useDesignable();
const options = useOptions(propsOptions);
const defaultValue = useDefaultValue(propsDefaultValue);
const compile = useCompile();
const { t } = useTranslation();
return {
title: typeof title === 'function' ? title(t) : compile(title),
options,
useVisible,
value: _.get(filedSchema, schemaKey, defaultValue),
onChange(v) {
deepMerge(getNewSchema({ fieldSchema: filedSchema, schemaKey, value: v }));
},
};
},
};
};

View File

@ -0,0 +1,61 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { SchemaSettingsItemType, useCompile, useDesignable } from '@nocobase/client';
import { useFieldSchema } from '@formily/react';
import _ from 'lodash';
import { getNewSchema, useHookDefault } from './util';
import { TFunction, useTranslation } from 'react-i18next';
export interface CreateSwitchSchemaSettingsItemProps {
name: string;
title: string | ((t: TFunction<'translation', undefined>) => string);
schemaKey: string;
defaultValue?: boolean;
useDefaultValue?: () => boolean;
useVisible?: () => boolean;
}
/**
* create `switch` type schema settings item
*
* @internal
* @unstable
*/
export function createSwitchSettingsItem(options: CreateSwitchSchemaSettingsItemProps): SchemaSettingsItemType {
const {
name,
useVisible,
schemaKey,
title,
defaultValue: propsDefaultValue,
useDefaultValue = useHookDefault,
} = options;
return {
name,
useVisible,
type: 'switch',
useComponentProps() {
const filedSchema = useFieldSchema();
const { deepMerge } = useDesignable();
const defaultValue = useDefaultValue(propsDefaultValue);
const compile = useCompile();
const { t } = useTranslation();
return {
title: typeof title === 'function' ? title(t) : compile(title),
checked: !!_.get(filedSchema, schemaKey, defaultValue),
onChange(v) {
deepMerge(getNewSchema({ fieldSchema: filedSchema, schemaKey, value: v }));
},
};
},
};
}

View File

@ -0,0 +1,12 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
export * from './createSelectSettingsItem';
export * from './createSwitchSettingsItem';
export * from './createModalSettingsItem';

View File

@ -0,0 +1,39 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { ISchema } from '@formily/json-schema';
import _ from 'lodash';
type IGetNewSchema = {
fieldSchema: ISchema;
schemaKey: string;
value: any;
valueKeys?: string[];
};
export function getNewSchema(options: IGetNewSchema) {
const { fieldSchema, schemaKey, value, valueKeys } = options as any;
const schemaKeyArr = schemaKey.split('.');
const clonedSchema = _.cloneDeep(fieldSchema[schemaKeyArr[0]]);
if (value != undefined && typeof value === 'object') {
Object.keys(value).forEach((key) => {
if (valueKeys && !valueKeys.includes(key)) return;
_.set(clonedSchema, `${schemaKeyArr.slice(1)}.${key}`, value[key]);
});
} else {
_.set(clonedSchema, schemaKeyArr.slice(1), value);
}
return {
'x-uid': fieldSchema['x-uid'],
[schemaKeyArr[0]]: clonedSchema,
};
}
export const useHookDefault = (defaultValues?: any) => defaultValues;

View File

@ -9,7 +9,7 @@
import { createForm } from '@formily/core';
import { FormContext, useField, useFieldSchema } from '@formily/react';
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useCollectionManager_deprecated } from '../collection-manager';
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
import { useTableBlockParams } from '../modules/blocks/data-blocks/table';
@ -49,14 +49,29 @@ interface Props {
*/
collection?: string;
children?: any;
expandFlag?: boolean;
}
const InternalTableBlockProvider = (props: Props) => {
const { params, showIndex, dragSort, rowKey, childrenColumnName, fieldNames, ...others } = props;
const {
params,
showIndex,
dragSort,
rowKey,
childrenColumnName,
expandFlag: propsExpandFlag = false,
fieldNames,
...others
} = props;
const field: any = useField();
const { resource, service } = useBlockRequestContext();
const fieldSchema = useFieldSchema();
const [expandFlag, setExpandFlag] = useState(fieldNames ? true : false);
const [expandFlag, setExpandFlag] = useState(fieldNames || propsExpandFlag ? true : false);
useEffect(() => {
setExpandFlag(fieldNames || propsExpandFlag);
}, [fieldNames || propsExpandFlag]);
const allIncludesChildren = useMemo(() => {
const { treeTable } = fieldSchema?.['x-decorator-props'] || {};
const data = service?.data?.data;

View File

@ -61,10 +61,10 @@ export const TableSelectorParamsProvider = ({ params, children }: { params: Para
};
const InternalTableSelectorProvider = (props) => {
const { params, rowKey, extraFilter } = props;
const { params, rowKey, extraFilter, expandFlag: propsExpandFlag = false } = props;
const field = useField();
const { resource, service } = useBlockRequestContext();
const [expandFlag, setExpandFlag] = useState(false);
const [expandFlag, setExpandFlag] = useState(propsExpandFlag);
const parentRecordData = useCollectionParentRecordData();
// if (service.loading) {
// return <Spin />;

View File

@ -287,4 +287,8 @@ export class Collection {
hasField(name: SchemaKey) {
return !!this.getField(name);
}
isTitleField(field: CollectionFieldOptions) {
return this.app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterface(field.interface)?.titleUsable;
}
}

View File

@ -815,5 +815,216 @@
"template": "general",
"view": false,
"filterTargetKey": "id"
},
{
"key": "knautlbeoqj",
"name": "tree-collection",
"title": "tree-collection",
"inherit": false,
"hidden": false,
"description": null,
"fields": [
{
"key": "h4ixmucmj41",
"name": "parentId",
"type": "bigInt",
"interface": "integer",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"isForeignKey": true,
"uiSchema": {
"type": "number",
"title": "{{t(\"Parent ID\")}}",
"x-component": "InputNumber",
"x-read-pretty": true
}
},
{
"key": "noozlvzzcxs",
"name": "parent",
"type": "belongsTo",
"interface": "m2o",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"foreignKey": "parentId",
"treeParent": true,
"onDelete": "CASCADE",
"uiSchema": {
"title": "{{t(\"Parent\")}}",
"x-component": "AssociationField",
"x-component-props": {
"multiple": false,
"fieldNames": {
"label": "id",
"value": "id"
}
}
},
"target": "tree-collection",
"targetKey": "id"
},
{
"key": "0ozo4vvd28w",
"name": "children",
"type": "hasMany",
"interface": "o2m",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"foreignKey": "parentId",
"treeChildren": true,
"onDelete": "CASCADE",
"uiSchema": {
"title": "{{t(\"Children\")}}",
"x-component": "AssociationField",
"x-component-props": {
"multiple": true,
"fieldNames": {
"label": "id",
"value": "id"
}
}
},
"target": "tree-collection",
"targetKey": "id",
"sourceKey": "id"
},
{
"key": "dsqipnyzywx",
"name": "id",
"type": "bigInt",
"interface": "integer",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"autoIncrement": true,
"primaryKey": true,
"allowNull": false,
"uiSchema": {
"type": "number",
"title": "{{t(\"ID\")}}",
"x-component": "InputNumber",
"x-read-pretty": true
}
},
{
"key": "51j7ynbyajo",
"name": "createdAt",
"type": "date",
"interface": "createdAt",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"field": "createdAt",
"uiSchema": {
"type": "datetime",
"title": "{{t(\"Created at\")}}",
"x-component": "DatePicker",
"x-component-props": {},
"x-read-pretty": true
}
},
{
"key": "fn6dpvd9ms1",
"name": "createdBy",
"type": "belongsTo",
"interface": "createdBy",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"target": "users",
"foreignKey": "createdById",
"uiSchema": {
"type": "object",
"title": "{{t(\"Created by\")}}",
"x-component": "AssociationField",
"x-component-props": {
"fieldNames": {
"value": "id",
"label": "nickname"
}
},
"x-read-pretty": true
},
"targetKey": "id"
},
{
"key": "n7zolkpe5rh",
"name": "updatedAt",
"type": "date",
"interface": "updatedAt",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"field": "updatedAt",
"uiSchema": {
"type": "string",
"title": "{{t(\"Last updated at\")}}",
"x-component": "DatePicker",
"x-component-props": {},
"x-read-pretty": true
}
},
{
"key": "ogtun00ozu2",
"name": "updatedBy",
"type": "belongsTo",
"interface": "updatedBy",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"target": "users",
"foreignKey": "updatedById",
"uiSchema": {
"type": "object",
"title": "{{t(\"Last updated by\")}}",
"x-component": "AssociationField",
"x-component-props": {
"fieldNames": {
"value": "id",
"label": "nickname"
}
},
"x-read-pretty": true
},
"targetKey": "id"
},
{
"key": "4tiwdomlf6g",
"name": "title",
"type": "string",
"interface": "input",
"description": null,
"collectionName": "tree-collection",
"parentKey": null,
"reverseKey": null,
"uiSchema": {
"type": "string",
"x-component": "Input",
"title": "title"
}
}
],
"logging": true,
"autoGenId": true,
"createdAt": true,
"createdBy": true,
"updatedAt": true,
"updatedBy": true,
"template": "tree",
"view": false,
"tree": "adjacencyList",
"schema": "public",
"filterTargetKey": "id"
}
]

View File

@ -283,5 +283,148 @@
"destroy": [1, 2]
}
}
},
"tree-collection:list": {
"data": [
{
"id": 1,
"createdAt": "2024-06-05T02:58:23.961Z",
"updatedAt": "2024-06-05T02:58:23.961Z",
"parentId": null,
"createdById": 1,
"updatedById": 1,
"title": "title-0",
"children": [
{
"id": 4,
"createdAt": "2024-06-05T02:59:21.455Z",
"updatedAt": "2024-06-05T02:59:21.462Z",
"parentId": 1,
"createdById": 1,
"updatedById": 1,
"title": "title-0-0",
"children": [
{
"id": 10,
"createdAt": "2024-06-05T03:00:14.122Z",
"updatedAt": "2024-06-05T03:00:14.129Z",
"parentId": 4,
"createdById": 1,
"updatedById": 1,
"title": "title-0-0-0",
"__index": "0.children.0.children.0"
},
{
"id": 11,
"createdAt": "2024-06-05T03:00:21.816Z",
"updatedAt": "2024-06-05T03:00:21.822Z",
"parentId": 4,
"createdById": 1,
"updatedById": 1,
"title": "title-0-0-1",
"__index": "0.children.0.children.1"
},
{
"id": 12,
"createdAt": "2024-06-05T03:00:34.253Z",
"updatedAt": "2024-06-05T03:00:34.261Z",
"parentId": 4,
"createdById": 1,
"updatedById": 1,
"title": "title-0-0-2",
"__index": "0.children.0.children.2"
}
],
"__index": "0.children.0"
},
{
"id": 5,
"createdAt": "2024-06-05T02:59:27.667Z",
"updatedAt": "2024-06-05T02:59:27.676Z",
"parentId": 1,
"createdById": 1,
"updatedById": 1,
"title": "title-0-1",
"__index": "0.children.1"
},
{
"id": 6,
"createdAt": "2024-06-05T02:59:33.167Z",
"updatedAt": "2024-06-05T02:59:33.174Z",
"parentId": 1,
"createdById": 1,
"updatedById": 1,
"title": "title-0-2",
"__index": "0.children.2"
}
],
"__index": "0"
},
{
"id": 2,
"createdAt": "2024-06-05T02:58:32.009Z",
"updatedAt": "2024-06-05T02:58:32.009Z",
"parentId": null,
"createdById": 1,
"updatedById": 1,
"title": "title-1",
"children": [
{
"id": 7,
"createdAt": "2024-06-05T02:59:39.049Z",
"updatedAt": "2024-06-05T02:59:39.055Z",
"parentId": 2,
"createdById": 1,
"updatedById": 1,
"title": "title-1-0",
"__index": "1.children.0"
},
{
"id": 8,
"createdAt": "2024-06-05T02:59:47.298Z",
"updatedAt": "2024-06-05T02:59:47.305Z",
"parentId": 2,
"createdById": 1,
"updatedById": 1,
"title": "title-1-1",
"__index": "1.children.1"
}
],
"__index": "1"
},
{
"id": 3,
"createdAt": "2024-06-05T02:58:34.289Z",
"updatedAt": "2024-06-05T02:58:34.289Z",
"parentId": null,
"createdById": 1,
"updatedById": 1,
"title": "title-2",
"children": [
{
"id": 9,
"createdAt": "2024-06-05T02:59:54.611Z",
"updatedAt": "2024-06-05T02:59:54.617Z",
"parentId": 3,
"createdById": 1,
"updatedById": 1,
"title": "title-2-0",
"__index": "2.children.0"
}
],
"__index": "2"
}
],
"meta": {
"count": 3,
"page": 1,
"pageSize": 20,
"totalPage": 1,
"allowedActions": {
"view": [1, 4, 10, 11, 12, 5, 6, 2, 7, 8, 3, 9],
"update": [1, 4, 10, 11, 12, 5, 6, 2, 7, 8, 3, 9],
"destroy": [1, 4, 10, 11, 12, 5, 6, 2, 7, 8, 3, 9]
}
}
}
}

View File

@ -15,7 +15,9 @@ import {
AntdSchemaComponentPlugin,
Application,
ApplicationOptions,
BlockSchemaToolbar,
CollectionPlugin,
DataBlockInitializer,
LocalDataSource,
SchemaSettingsPlugin,
} from '../index';
@ -108,7 +110,11 @@ export const mockApp = (options: MockAppOptions) => {
app.pluginManager.add(AntdSchemaComponentPlugin);
app.pluginManager.add(SchemaSettingsPlugin);
app.pluginManager.add(CollectionPlugin, { config: { enableRemoteDataSource: false } });
app.addComponents({ ActionInitializer });
app.addComponents({
ActionInitializer,
DataBlockInitializer,
BlockSchemaToolbar,
});
const apis = Object.assign({}, defaultApis, optionsApis);

View File

@ -190,7 +190,7 @@ export const useFilterAPI = () => {
useEffect(() => {
setIsConnected(targets && targets.some((target) => dataBlocks.some((dataBlock) => dataBlock.uid === target.uid)));
}, [targetsKeys.length, dataBlocks]);
}, [targetsKeys.length, targets, dataBlocks]);
const doFilter = useCallback(
(
@ -242,7 +242,7 @@ export const useFilterAPI = () => {
);
});
},
[dataBlocks],
[dataBlocks, targets, uid],
);
return {

View File

@ -828,5 +828,6 @@
"Drag and drop sorting field": "Drag and drop sorting field",
"This variable has been deprecated and can be replaced with \"Current form\"": "This variable has been deprecated and can be replaced with \"Current form\"",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.",
"URL search params": "URL search params"
"URL search params": "URL search params",
"Expand All": "Expand All"
}

View File

@ -760,5 +760,6 @@
"License": "Licencia",
"This variable has been deprecated and can be replaced with \"Current form\"": "La variable ha sido obsoleta; \"Formulario actual\" puede ser utilizada como sustituto",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "El valor de esta variable se deriva de la cadena de consulta de la URL de la página. Esta variable sólo puede utilizarse normalmente cuando la página tiene una cadena de consulta.",
"URL search params": "Parámetros de búsqueda de URL"
"URL search params": "Parámetros de búsqueda de URL",
"Expand All": "Expandir todo"
}

View File

@ -780,5 +780,6 @@
"License": "Licence",
"This variable has been deprecated and can be replaced with \"Current form\"": "La variable a été obsolète ; \"Formulaire actuel\" peut être utilisé comme substitut",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "La valeur de cette variable est dérivée de la chaîne de requête de l'URL de la page. Cette variable ne peut être utilisée normalement que lorsque la page a une chaîne de requête.",
"URL search params": "Paramètres de recherche d'URL"
"URL search params": "Paramètres de recherche d'URL",
"Expand All": "Tout déplier"
}

View File

@ -699,5 +699,6 @@
"License": "ライセンス",
"This variable has been deprecated and can be replaced with \"Current form\"": "この変数は非推奨です。代わりに「現在のフォーム」を使用してください",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "この変数の値はページURLのクエリ文字列から取得されます。この変数は、ページにクエリ文字列がある場合にのみ正常に使用できます。",
"URL search params": "URL検索パラメータ"
"URL search params": "URL検索パラメータ",
"Expand All": "すべて展開"
}

View File

@ -871,5 +871,6 @@
"License": "라이선스",
"This variable has been deprecated and can be replaced with \"Current form\"": "변수가 폐기되었습니다. \"현재 폼\"을 대체로 사용할 수 있습니다",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "이 변수의 값은 페이지 URL의 쿼리 문자열에서 파생됩니다. 이 변수는 페이지에 쿼리 문자열이 있는 경우에만 정상적으로 사용할 수 있습니다.",
"URL search params": "URL 검색 매개변수"
"URL search params": "URL 검색 매개변수",
"Expand All": "모두 펼치기"
}

View File

@ -736,5 +736,6 @@
"License": "Licença",
"This variable has been deprecated and can be replaced with \"Current form\"": "A variável foi descontinuada; \"Formulário atual\" pode ser usada como substituto",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "O valor desta variável é derivado da string de consulta da URL da página. Esta variável só pode ser usada normalmente quando a página tem uma string de consulta.",
"URL search params": "Parâmetros de pesquisa de URL"
"URL search params": "Parâmetros de pesquisa de URL",
"Expand All": "Expandir tudo"
}

View File

@ -574,5 +574,6 @@
"License": "Лицензия",
"This variable has been deprecated and can be replaced with \"Current form\"": "Переменная устарела; \"Текущая форма\" может быть использована в качестве замены",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "Значение этой переменной происходит из строки запроса URL страницы. Эта переменная может использоваться только в том случае, если у страницы есть строка запроса.",
"URL search params": "Параметры поиска URL"
"URL search params": "Параметры поиска URL",
"Expand All": "Развернуть все"
}

View File

@ -572,5 +572,6 @@
"License": "Lisans",
"This variable has been deprecated and can be replaced with \"Current form\"": "Değişken kullanımdan kaldırıldı; \"Geçerli form\" yerine kullanılabilir",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "Bu değişkenin değeri sayfa URL'sinin sorgu dizgisinden türetilir. Bu değişken, sayfanın bir sorgu dizgisi olduğunda yalnızca normal olarak kullanılabilir.",
"URL search params": "URL arama parametreleri"
"URL search params": "URL arama parametreleri",
"Expand All": "Tümünü genişlet"
}

View File

@ -780,5 +780,6 @@
"License": "Ліцензія",
"This variable has been deprecated and can be replaced with \"Current form\"": "Змінна була застарілою; \"Поточна форма\" може бути використана як заміна",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "Значення цієї змінної походить з рядка запиту URL-адреси сторінки. Цю змінну можна використовувати нормально лише тоді, коли у сторінки є рядок запиту.",
"URL search params": "Параметри пошуку URL"
"URL search params": "Параметри пошуку URL",
"Expand All": "Розгорнути все"
}

View File

@ -957,5 +957,6 @@
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "该变量的值是根据页面 URL 的 query string 得来的,只有当页面存在 query string 的时候,该变量才能正常使用。",
"Edit link":"编辑链接",
"Add parameter":"添加参数",
"URL search params": "URL 查询参数"
"URL search params": "URL 查询参数",
"Expand All": "展开全部"
}

View File

@ -869,5 +869,6 @@
"License": "許可證",
"This variable has been deprecated and can be replaced with \"Current form\"": "該變數已被棄用,可以使用“當前表單”作為替代",
"The value of this variable is derived from the query string of the page URL. This variable can only be used normally when the page has a query string.": "該變數的值來自頁面 URL 的查詢字符串,只有當頁面有查詢字符串時,該變數才能正常使用。",
"URL search params": "URL 查詢參數"
"URL search params": "URL 查詢參數",
"Expand All": "展開全部"
}

View File

@ -7,29 +7,23 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { ArrayItems } from '@formily/antd-v5';
import { ISchema } from '@formily/json-schema';
import { useField, useFieldSchema } from '@formily/react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../../../api-client';
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
import { useTableBlockContext } from '../../../../block-provider';
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
import {
useCollectionManager_deprecated,
useCollection_deprecated,
useSortFields,
} from '../../../../collection-manager';
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../../collection-manager';
import { FilterBlockType } from '../../../../filter-provider/utils';
import { removeNullCondition, useDesignable } from '../../../../schema-component';
import { useDesignable } from '../../../../schema-component';
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks';
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
import { SchemaSettingsSortField } from '../../../../schema-settings/SchemaSettingsSortField';
import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate';
import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem';
import { setDefaultSortingRulesSchemaSettingsItem } from '../../../../schema-settings/setDefaultSortingRulesSchemaSettingsItem';
import { setTheDataScopeSchemaSettingsItem } from '../../../../schema-settings/setTheDataScopeSchemaSettingsItem';
import { createSwitchSettingsItem } from '../../../../application/schema-settings/utils';
export const tableBlockSettings = new SchemaSettings({
name: 'blockSettings:table',
@ -66,6 +60,11 @@ export const tableBlockSettings = new SchemaSettings({
onChange: (flag) => {
field.decoratorProps.treeTable = flag;
fieldSchema['x-decorator-props'].treeTable = flag;
if (flag === false) {
fieldSchema['x-decorator-props'].expandFlag = false;
}
const params = {
...service.params?.[0],
tree: flag ? true : null,
@ -88,6 +87,16 @@ export const tableBlockSettings = new SchemaSettings({
return collection?.tree && collectionField?.collectionName === collectionField?.target;
},
},
createSwitchSettingsItem({
name: 'expandFlag',
title: (t) => t('Expand All'),
defaultValue: false,
schemaKey: 'x-decorator-props.expandFlag',
useVisible() {
const field = useField();
return field.decoratorProps.treeTable;
},
}),
{
name: 'enableDragAndDropSorting',
type: 'switch',
@ -137,165 +146,8 @@ export const tableBlockSettings = new SchemaSettings({
return field.decoratorProps.dragSort;
},
},
{
name: 'SetTheDataScope',
Component: SchemaSettingsDataScope,
useComponentProps: () => {
const { name } = useCollection_deprecated();
const field = useField();
const fieldSchema = useFieldSchema();
const { form } = useFormBlockContext();
const { service } = useTableBlockContext();
const { dn } = useDesignable();
const onDataScopeSubmit = useCallback(
({ filter }) => {
filter = removeNullCondition(filter);
const params = field.decoratorProps.params || {};
params.filter = filter;
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.params[0].page = 1;
},
[dn, field.decoratorProps, fieldSchema, service],
);
return {
collectionName: name,
defaultFilter: fieldSchema?.['x-decorator-props']?.params?.filter || {},
form: form,
onSubmit: onDataScopeSubmit,
};
},
},
{
name: 'SetDefaultSortingRules',
type: 'modal',
useComponentProps() {
const { name } = useCollection_deprecated();
const field = useField();
const fieldSchema = useFieldSchema();
const sortFields = useSortFields(name);
const { service } = useTableBlockContext();
const { t } = useTranslation();
const { dn } = useDesignable();
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 });
},
};
},
useVisible() {
const field = useField();
const { dragSort } = field.decoratorProps;
return !dragSort;
},
},
setTheDataScopeSchemaSettingsItem,
setDefaultSortingRulesSchemaSettingsItem,
setDataLoadingModeSettingsItem,
{
name: 'RecordsPerPage',

View File

@ -17,7 +17,7 @@ import { BlockItemCard } from '../block-item/BlockItemCard';
import { BlockItemError } from '../block-item/BlockItemError';
import useStyles from './style';
interface CardItemProps extends CardProps {
export interface CardItemProps extends CardProps {
name?: string;
children?: React.ReactNode;
/**
@ -27,6 +27,7 @@ interface CardItemProps extends CardProps {
*/
lazyRender?: IntersectionOptions & { element?: React.JSX.Element };
heightMode?: string;
height?: number;
}
export const CardItem: FC<CardItemProps> = (props) => {

View File

@ -455,7 +455,7 @@ export const Table: any = withDynamicSchemaProps(
if (!_.isEqual(newExpandesKeys, expandedKeys)) {
setExpandesKeys(newExpandesKeys);
}
}, [expandFlag]);
}, [expandFlag, allIncludesChildren]);
/**
* key key

View File

@ -0,0 +1,86 @@
import React from 'react';
import { TableBlockProvider, useTableBlockProps, SchemaComponent, Plugin, ISchema } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
const schema: ISchema = {
type: 'void',
name: 'root',
properties: {
test: {
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-decorator-props': {
collection: 'tree-collection',
action: 'list',
params: {
tree: true,
pageSize: 2,
},
treeTable: true,
showIndex: true,
expandFlag: true,
dragSort: false,
},
properties: {
table: {
type: 'array',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps', // 自动注入 Table 所需的 props
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
properties: {
column1: {
type: 'void',
title: 'Role UID',
'x-component': 'TableV2.Column',
properties: {
name: {
type: 'string',
'x-component': 'CollectionField',
'x-pattern': 'readPretty', // 这里要设置为 true
},
},
},
column2: {
type: 'void',
title: 'Role name',
'x-component': 'TableV2.Column',
properties: {
title: {
type: 'string',
'x-component': 'CollectionField',
'x-pattern': 'readPretty',
},
},
},
},
},
},
},
},
};
const Demo = () => {
return <SchemaComponent schema={schema} />;
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
const app = mockApp({
plugins: [DemoPlugin],
components: {
TableBlockProvider,
},
scopes: {
useTableBlockProps,
},
});
export default app.getRootComponent();

View File

@ -0,0 +1,85 @@
import React from 'react';
import { TableBlockProvider, useTableBlockProps, SchemaComponent, Plugin, ISchema } from '@nocobase/client';
import { mockApp } from '@nocobase/client/demo-utils';
const schema: ISchema = {
type: 'void',
name: 'root',
properties: {
test: {
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-decorator-props': {
collection: 'tree-collection',
action: 'list',
params: {
tree: true,
pageSize: 2,
},
treeTable: true,
showIndex: true,
dragSort: false,
},
properties: {
table: {
type: 'array',
'x-component': 'TableV2',
'x-use-component-props': 'useTableBlockProps', // 自动注入 Table 所需的 props
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
properties: {
column1: {
type: 'void',
title: 'Role UID',
'x-component': 'TableV2.Column',
properties: {
name: {
type: 'string',
'x-component': 'CollectionField',
'x-pattern': 'readPretty', // 这里要设置为 true
},
},
},
column2: {
type: 'void',
title: 'Role name',
'x-component': 'TableV2.Column',
properties: {
title: {
type: 'string',
'x-component': 'CollectionField',
'x-pattern': 'readPretty',
},
},
},
},
},
},
},
},
};
const Demo = () => {
return <SchemaComponent schema={schema} />;
};
class DemoPlugin extends Plugin {
async load() {
this.app.router.add('root', { path: '/', Component: Demo });
}
}
const app = mockApp({
plugins: [DemoPlugin],
components: {
TableBlockProvider,
},
scopes: {
useTableBlockProps,
},
});
export default app.getRootComponent();

View File

@ -40,3 +40,31 @@ Table 会将当前行的数据通过 [CollectionRecordProvider](/core/data-block
在编辑场景下,在通过 [useDataBlockResource()](/core/data-block/data-block-resource-provider) 和 [useDataBlockRequest()](/core/data-block/data-block-request-provider#usedatablockrequest) 对数据进行更新,以及对列表进行刷新。
<code src="./demos/new-demos/record.tsx"></code>
## Tree
### Basic
树表需要设置:
```diff
'x-decorator-props': {
collection: 'tree-collection',
action: 'list',
params: {
+ tree: true,
pageSize: 2,
},
+ treeTable: true,
showIndex: true,
dragSort: false,
},
```
<code src="./demos/new-demos/tree.tsx"></code>
### expandFlag
默认展开树表的节点,需要设置 `expandFlag` 属性。
<code src="./demos/new-demos/tree-expandFlag.tsx"></code>

View File

@ -108,16 +108,30 @@ const useTableHeight = () => {
};
// 常规数据区块高度计算
export const useDataBlockHeight = () => {
interface UseDataBlockHeightOptions {
removeBlockHeaderHeight?: boolean;
innerExtraHeight?: number;
}
export const useDataBlockHeight = (options?: UseDataBlockHeightOptions) => {
const { heightProps } = useBlockHeightProps();
const pageFullScreenHeight = useFullScreenHeight();
const { heightMode, height } = heightProps || {};
const { token } = theme.useToken();
const { heightMode, height, title } = heightProps || {};
const blockHeaderHeight = title ? token.fontSizeLG * token.lineHeightLG + token.padding * 2 - 1 : 0;
if (!heightProps?.heightMode || heightMode === HeightMode.DEFAULT) {
return;
}
if (heightMode === HeightMode.FULL_HEIGHT) {
return window.innerHeight - pageFullScreenHeight;
let res = window.innerHeight - pageFullScreenHeight;
if (options?.removeBlockHeaderHeight) {
res = res - blockHeaderHeight;
}
if (options?.innerExtraHeight) {
res = res - options.innerExtraHeight;
}
return res;
}
return height;
};

View File

@ -9,7 +9,7 @@
import Icon, { TableOutlined } from '@ant-design/icons';
import { Divider, Empty, Input, MenuProps, Spin } from 'antd';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
SchemaInitializerItem,
@ -305,9 +305,10 @@ export interface DataBlockInitializerProps {
currentText?: string;
/** 用于更改 Other records 的文案 */
otherText?: string;
children?: React.ReactNode;
}
export const DataBlockInitializer = (props: DataBlockInitializerProps) => {
export const DataBlockInitializer: FC<DataBlockInitializerProps> = (props) => {
const {
templateWrap,
onCreateBlockSchema,

View File

@ -151,6 +151,7 @@ export const SchemaSettingsProvider: React.FC<SchemaSettingsProviderProps> = (pr
export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = (props) => {
const { title, dn, ...others } = props;
const app = useApp();
const [visible, setVisible] = useState(false);
const { Component, getMenuItems } = useMenuItem();
const [, startTransition] = useReactTransition();

View File

@ -24,8 +24,8 @@ import { useBlockTemplateContext } from '../schema-templates/BlockTemplate';
import { SchemaSettingsItem, useSchemaSettings } from './SchemaSettings';
export function SchemaSettingsTemplate(props) {
const { componentName, collectionName, resourceName, needRender } = props;
const { t } = useTranslation();
const { componentName, collectionName, resourceName, needRender, useTranslationHooks = useTranslation } = props;
const { t } = useTranslationHooks();
const { getCollection } = useCollectionManager_deprecated();
const { dn, setVisible, template, fieldSchema } = useSchemaSettings();
const compile = useCompile();

View File

@ -20,6 +20,8 @@ export * from './SchemaSettingsNumberFormat';
export * from './SchemaSettingsSortingRule';
export * from './SchemaSettingsTemplate';
export * from './SchemaSettingsBlockHeightItem';
export * from './setDefaultSortingRulesSchemaSettingsItem';
export * from './setTheDataScopeSchemaSettingsItem';
export * from './hooks/useGetAriaLabelOfDesigner';
export * from './hooks/useIsAllowToSetDefaultValue';
export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFilter';

View File

@ -0,0 +1,140 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { ArrayItems } from '@formily/antd-v5';
import { ISchema } from '@formily/json-schema';
import { useField, useFieldSchema } from '@formily/react';
import { useTranslation } from 'react-i18next';
import { useTableBlockContext } from '../block-provider';
import { useCollection_deprecated, useSortFields } from '../collection-manager';
import { useDesignable } from '../schema-component';
import { SchemaSettingsItemType } from '../application';
export const setDefaultSortingRulesSchemaSettingsItem: SchemaSettingsItemType = {
name: 'SetDefaultSortingRules',
type: 'modal',
useComponentProps() {
const { name } = useCollection_deprecated();
const field = useField();
const fieldSchema = useFieldSchema();
const sortFields = useSortFields(name);
const { service } = useTableBlockContext();
const { t } = useTranslation();
const { dn } = useDesignable();
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 });
},
};
},
useVisible() {
const field = useField();
const { dragSort } = field.decoratorProps;
return !dragSort;
},
};

View File

@ -0,0 +1,54 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { useField, useFieldSchema } from '@formily/react';
import { useCallback } from 'react';
import { SchemaSettingsItemType } from '../application';
import { useFormBlockContext, useTableBlockContext } from '../block-provider';
import { useCollection_deprecated } from '../collection-manager';
import { useDesignable, removeNullCondition } from '../schema-component';
import { SchemaSettingsDataScope } from './SchemaSettingsDataScope';
export const setTheDataScopeSchemaSettingsItem: SchemaSettingsItemType = {
name: 'SetTheDataScope',
Component: SchemaSettingsDataScope,
useComponentProps: () => {
const { name } = useCollection_deprecated();
const field = useField();
const fieldSchema = useFieldSchema();
const { form } = useFormBlockContext();
const { service } = useTableBlockContext();
const { dn } = useDesignable();
const onDataScopeSubmit = useCallback(
({ filter }) => {
filter = removeNullCondition(filter);
const params = field.decoratorProps.params || {};
params.filter = filter;
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.params[0].page = 1;
},
[dn, field.decoratorProps, fieldSchema, service],
);
return {
collectionName: name,
defaultFilter: fieldSchema?.['x-decorator-props']?.params?.filter || {},
form: form,
onSubmit: onDataScopeSubmit,
};
},
};

View File

@ -1,3 +1,12 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { Plugin } from '@nocobase/client';
export class PluginCollectionSqlClient extends Plugin {
@ -9,7 +18,6 @@ export class PluginCollectionSqlClient extends Plugin {
// You can get and modify the app instance here
async load() {
console.log(this.app);
// this.app.addComponents({})
// this.app.addScopes({})
// this.app.addProvider()

9219
yarn.lock

File diff suppressed because it is too large Load Diff