fix: data-block docs and bug

This commit is contained in:
dream2023 2024-01-25 18:11:06 +08:00
parent 591b9d6dc5
commit 44ad995d81
32 changed files with 476 additions and 1664 deletions

View File

@ -1,486 +0,0 @@
[
{
"key": "h7b9i8khc3q",
"name": "users",
"inherit": false,
"hidden": false,
"description": null,
"category": [],
"namespace": "users.users",
"duplicator": {
"dumpable": "optional",
"with": "rolesUsers"
},
"sortable": "sort",
"model": "UserModel",
"createdBy": true,
"updatedBy": true,
"logging": true,
"from": "db2cm",
"title": "{{t(\"Users\")}}",
"rawTitle": "{{t(\"Users\")}}",
"fields": [
{
"uiSchema": {
"type": "number",
"title": "{{t(\"ID\")}}",
"x-component": "InputNumber",
"x-read-pretty": true,
"rawTitle": "{{t(\"ID\")}}"
},
"key": "ffp1f2sula0",
"name": "id",
"type": "bigInt",
"interface": "id",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"autoIncrement": true,
"primaryKey": true,
"allowNull": false
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Nickname\")}}",
"x-component": "Input",
"rawTitle": "{{t(\"Nickname\")}}"
},
"key": "vrv7yjue90g",
"name": "nickname",
"type": "string",
"interface": "input",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Username\")}}",
"x-component": "Input",
"x-validator": {
"username": true
},
"required": true,
"rawTitle": "{{t(\"Username\")}}"
},
"key": "2ccs6evyrub",
"name": "username",
"type": "string",
"interface": "input",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"unique": true
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Email\")}}",
"x-component": "Input",
"x-validator": "email",
"required": true,
"rawTitle": "{{t(\"Email\")}}"
},
"key": "rrskwjl5wt1",
"name": "email",
"type": "string",
"interface": "email",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"unique": true
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Phone\")}}",
"x-component": "Input",
"x-validator": "phone",
"required": true,
"rawTitle": "{{t(\"Phone\")}}"
},
"key": "yzv4yjeg0pn",
"name": "phone",
"type": "string",
"interface": "phone",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"unique": true
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Password\")}}",
"x-component": "Password",
"rawTitle": "{{t(\"Password\")}}"
},
"key": "3pzwulm5o6b",
"name": "password",
"type": "password",
"interface": "password",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"hidden": true
},
{
"key": "q3is17d1abv",
"name": "appLang",
"type": "string",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null
},
{
"key": "lhysy09i27u",
"name": "resetToken",
"type": "string",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"unique": true,
"hidden": true
},
{
"key": "xlbup0n8dr5",
"name": "systemSettings",
"type": "json",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"defaultValue": {}
},
{
"key": "w1vjr3cu2dk",
"name": "sort",
"type": "sort",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"hidden": true
},
{
"key": "p3tw5siagb4",
"name": "createdById",
"type": "context",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"dataType": "bigInt",
"dataIndex": "state.currentUser.id",
"createOnly": true,
"visible": true,
"index": true
},
{
"key": "1j6mx2762or",
"name": "createdBy",
"type": "belongsTo",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"target": "users",
"foreignKey": "createdById",
"targetKey": "id"
},
{
"key": "k91xpvbvwle",
"name": "updatedById",
"type": "context",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"dataType": "bigInt",
"dataIndex": "state.currentUser.id",
"visible": true,
"index": true
},
{
"key": "xfog81yb136",
"name": "updatedBy",
"type": "belongsTo",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"target": "users",
"foreignKey": "updatedById",
"targetKey": "id"
},
{
"uiSchema": {
"type": "array",
"title": "{{t(\"Roles\")}}",
"x-component": "AssociationField",
"x-component-props": {
"multiple": true,
"fieldNames": {
"label": "title",
"value": "name"
}
},
"rawTitle": "{{t(\"Roles\")}}"
},
"key": "b7s9ywt6n06",
"name": "roles",
"type": "belongsToMany",
"interface": "m2m",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"target": "roles",
"foreignKey": "userId",
"otherKey": "roleName",
"onDelete": "CASCADE",
"sourceKey": "id",
"targetKey": "name",
"through": "rolesUsers"
},
{
"key": "0j83jlhhi9r",
"name": "jobs",
"type": "belongsToMany",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"through": "users_jobs",
"onDelete": "CASCADE",
"foreignKey": "userId",
"sourceKey": "id",
"otherKey": "jobId",
"targetKey": "id"
},
{
"key": "h7s78jvbo89",
"name": "usersJobs",
"type": "hasMany",
"interface": null,
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"target": "users_jobs",
"foreignKey": "userId",
"sourceKey": "id",
"targetKey": "id"
}
]
},
{
"key": "pqnenvqrzxr",
"name": "roles",
"inherit": false,
"hidden": false,
"description": null,
"category": [],
"namespace": "acl.acl",
"duplicator": {
"dumpable": "required",
"with": "uiSchemas"
},
"autoGenId": false,
"model": "RoleModel",
"filterTargetKey": "name",
"sortable": true,
"from": "db2cm",
"title": "{{t(\"Roles\")}}",
"rawTitle": "{{t(\"Roles\")}}",
"fields": [
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Role UID\")}}",
"x-component": "Input",
"rawTitle": "{{t(\"Role UID\")}}"
},
"key": "jbz9m80bxmp",
"name": "name",
"type": "uid",
"interface": "input",
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"prefix": "r_",
"primaryKey": true
},
{
"uiSchema": {
"type": "string",
"title": "{{t(\"Role name\")}}",
"x-component": "Input",
"rawTitle": "{{t(\"Role name\")}}"
},
"key": "faywtz4sf3u",
"name": "title",
"type": "string",
"interface": "input",
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"unique": true,
"translation": true
},
{
"key": "1enkovm9sye",
"name": "description",
"type": "string",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null
},
{
"key": "wwoozt6911x",
"name": "strategy",
"type": "json",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null
},
{
"key": "wupe6r46azg",
"name": "default",
"type": "boolean",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"defaultValue": false
},
{
"key": "1wxu26hqcey",
"name": "hidden",
"type": "boolean",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"defaultValue": false
},
{
"key": "krspuzq44fc",
"name": "allowConfigure",
"type": "boolean",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null
},
{
"key": "mv8vmxdj2wd",
"name": "allowNewMenu",
"type": "boolean",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null
},
{
"key": "c81x5luikwd",
"name": "menuUiSchemas",
"type": "belongsToMany",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"target": "uiSchemas",
"targetKey": "x-uid",
"onDelete": "CASCADE",
"foreignKey": "roleName",
"sourceKey": "name",
"otherKey": "uiSchemaXUid",
"through": "rolesUischemas"
},
{
"key": "lm9x55l6gxm",
"name": "resources",
"type": "hasMany",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"target": "rolesResources",
"sourceKey": "name",
"targetKey": "name",
"foreignKey": "roleName"
},
{
"key": "5dnal1v7se3",
"name": "snippets",
"type": "set",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"defaultValue": ["!ui.*", "!pm", "!pm.*"]
},
{
"key": "foxkspl2zt7",
"name": "users",
"type": "belongsToMany",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"target": "users",
"foreignKey": "roleName",
"otherKey": "userId",
"onDelete": "CASCADE",
"sourceKey": "name",
"targetKey": "id",
"through": "rolesUsers"
},
{
"key": "n3937y2slrt",
"name": "sort",
"type": "sort",
"interface": null,
"description": null,
"collectionName": "roles",
"parentKey": null,
"reverseKey": null,
"hidden": true
}
]
}
]

View File

@ -1,38 +0,0 @@
import { Application, ApplicationOptions, CollectionManagerProvider } from '@nocobase/client';
import MockAdapter from 'axios-mock-adapter';
import React, { ComponentType } from 'react';
import collections from './collections.json';
export function createApp(Demo: ComponentType<any>, options: ApplicationOptions, mocks: Record<string, any> = {}) {
const Provider = () => {
return (
<CollectionManagerProvider collections={collections as any}>
<Demo />
</CollectionManagerProvider>
);
};
const app = new Application({
apiClient: {
baseURL: 'http://localhost:8000',
},
providers: [Provider],
...options,
});
const mock = new MockAdapter(app.apiClient.axios);
Object.entries(mocks).forEach(([url, data]) => {
mock.onGet(url).reply(async (config) => {
const res = typeof data === 'function' ? data(config) : data;
return [200, res];
});
mock.onPost(url).reply(async (config) => {
const res = typeof data === 'function' ? data(config) : data;
return [200, res];
});
});
const Root = app.getRootComponent();
return Root;
}

View File

@ -1,141 +0,0 @@
import React, { FC } from 'react';
import {
SchemaComponent,
useDataBlockRequestV2,
useBlockSettingsV2,
withSchemaComponentProps,
UseDataBlockProps,
} from '@nocobase/client';
import { createApp } from './createApp';
import { Switch, Table, TableProps } from 'antd';
interface DemoTableRecordType {
name: string;
}
type DemoTableProps = TableProps<DemoTableRecordType>;
const DemoTable: FC<DemoTableProps> = withSchemaComponentProps((props) => {
const { dn } = useBlockSettingsV2();
return (
<>
<Switch
defaultChecked={props.bordered}
checkedChildren="Bordered"
onChange={(v) => {
dn.deepMerge({
'x-decorator-props': {
bordered: v,
},
});
}}
></Switch>
<Table {...props}></Table>
</>
);
});
function useDemoTableProps(): DemoTableProps {
const { data, loading } = useDataBlockRequestV2<{ data: DemoTableRecordType[]; total: number }>();
const { props, changeSchemaProps } = useBlockSettingsV2<{
rowKey?: string;
params?: Record<string, any>;
bordered?: boolean;
}>();
const { rowKey, params, bordered } = props;
return {
columns: [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
],
loading,
dataSource: data?.data || [],
rowKey,
bordered,
pagination: {
pageSize: params.pageSize || 5,
current: params.page || 1,
total: data?.total,
onChange(page, pageSize) {
changeSchemaProps({
params: {
pageSize,
page,
},
});
},
},
};
}
const collection = 'users';
const action = 'list';
const useTableDataBlockDecoratorProps: UseDataBlockProps<'CollectionList'> = () => {
return {
params: {
address: 'New York',
},
};
};
const schema = {
type: 'void',
name: 'hello',
'x-decorator': 'DataBlockProviderV2',
'x-use-decorator-props': 'useTableDataBlockDecoratorProps',
'x-component': 'DemoTable',
'x-use-component-props': 'useDemoTableProps',
'x-decorator-props': {
action: action,
collection: collection,
params: {
pageSize: 5,
page: 1,
},
rowKey: 'id',
bordered: false,
},
};
const Demo = () => {
return <SchemaComponent schema={schema}></SchemaComponent>;
};
const mocks = {
[`${collection}:${action}`]: function (config: any) {
console.log('请求结果');
const { page = 1, pageSize, address } = config.params;
const fixedData = [];
for (let i = 0; i < pageSize; i += 1) {
fixedData.push({
id: (page - 1) * pageSize + i + 1,
name: ['Light', 'Bamboo', 'Little'][i % 3],
address: `${address} No. ${i + 1} Lake Park`,
});
}
return {
data: fixedData,
total: 200,
};
},
};
const Root = createApp(
Demo,
{ components: { DemoTable }, scopes: { useDemoTableProps, useTableDataBlockDecoratorProps } },
mocks,
);
export default Root;

View File

@ -1,140 +0,0 @@
import React, { FC } from 'react';
import {
RecordV2,
RecordProviderV2,
SchemaComponent,
useDataBlockRequestV2,
useBlockSettingsV2,
withSchemaComponentProps,
useDesignable,
} from '@nocobase/client';
import { createApp } from './createApp';
import { Switch, Table, TableProps } from 'antd';
interface DemoTableRecordType {
name: string;
}
type DemoTableProps = TableProps<DemoTableRecordType>;
const DemoTable: FC<DemoTableProps> = withSchemaComponentProps((props) => {
const { dn } = useDesignable();
return (
<>
<Switch
defaultChecked={props.bordered}
checkedChildren="Bordered"
onChange={(v) => {
dn.deepMerge({
'x-decorator-props': {
bordered: v,
},
});
}}
></Switch>
<Table {...props}></Table>
</>
);
});
function useDemoTableProps(): DemoTableProps {
const { data, loading } = useDataBlockRequestV2<{ data: DemoTableRecordType[]; total: number }>();
const { rowKey, params, bordered } = useBlockSettingsV2<{ rowKey?: string; params?: Record<string, any> }>();
const { dn } = useDesignable();
return {
columns: [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: 'Name',
dataIndex: 'name',
key: 'name',
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
},
],
loading,
dataSource: data?.data || [],
rowKey,
bordered,
pagination: {
pageSize: params.pageSize || 5,
current: params.page || 1,
total: data?.total,
onChange(page, pageSize) {
dn.deepMerge({
'x-decorator-props': {
params: {
pageSize,
page,
},
},
});
},
},
};
}
const collection = 'users';
const action = 'list';
const associationA = 'a';
const associationB = 'b';
const association = `${associationA}.${associationB}`;
const schema = {
type: 'void',
name: 'hello',
'x-decorator': 'DataBlockProviderV2',
'x-component': 'DemoTable',
'x-use-component-props': 'useDemoTableProps',
'x-decorator-props': {
action: action,
params: {
pageSize: 5,
page: 1,
},
rowKey: 'id',
dragSort: false,
resource: collection,
showIndex: true,
collection: collection,
bordered: false,
association,
},
};
const Demo = () => {
const record = new RecordV2({ current: { sourceId: 1 } });
return (
<RecordProviderV2 record={record}>
<SchemaComponent schema={schema}></SchemaComponent>
</RecordProviderV2>
);
};
const mocks = {
[`${associationA}/1/${associationB}:${action}`]: function (config: any) {
console.log('请求结果');
const { page = 1, pageSize } = config.params;
const fixedData = [];
for (let i = 0; i < pageSize; i += 1) {
fixedData.push({
id: (page - 1) * pageSize + i + 1,
name: ['Light', 'Bamboo', 'Little'][i % 3],
address: `New York No. ${i + 1} Lake Park`,
});
}
return {
data: fixedData,
total: 200,
};
},
};
const Root = createApp(Demo, { components: { DemoTable }, scopes: { useDemoTableProps } }, mocks);
export default Root;

View File

@ -1,23 +0,0 @@
# Block Provider
## API
- collection-list
<code src="./demo1.tsx"></code>
- collection-get
<code src="./demo2.tsx"></code>
- collection-create
<code src="./demo3.tsx"></code>
- collection-record
<code src="./demo4.tsx"></code>
## 综合示例
<code src="./table/index.tsx"></code>

View File

@ -1,47 +0,0 @@
import { useField, useFieldSchema } from '@formily/react';
import { useCollection, useCollectionManager, useCompile, useSchemaToolbarRender } from '@nocobase/client';
import React, { useLayoutEffect } from 'react';
import { isCollectionFieldComponent } from './Table.useProps';
export const useColumnSchema = () => {
const { getField } = useCollection();
const compile = useCompile();
const columnSchema = useFieldSchema();
const { getCollectionJoinField } = useCollectionManager();
const fieldSchema = columnSchema.reduceProperties((buf, s) => {
if (isCollectionFieldComponent(s)) {
return s;
}
return buf;
}, null);
if (!fieldSchema) {
return {};
}
const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
return { columnSchema, fieldSchema, collectionField, uiSchema: compile(collectionField?.uiSchema) };
};
export const TableColumnDecorator = () => {
const field = useField();
const { fieldSchema, uiSchema } = useColumnSchema();
const { render } = useSchemaToolbarRender(fieldSchema);
const compile = useCompile();
useLayoutEffect(() => {
if (field.title) {
return;
}
if (!fieldSchema) {
return;
}
if (uiSchema?.title) {
field.title = uiSchema?.title;
}
}, [field, fieldSchema, uiSchema?.title]);
return (
<>
{render()}
<div role="button">{field?.title || compile(uiSchema?.title)}</div>
</>
);
};

View File

@ -1,130 +0,0 @@
import { Schema } from '@formily/json-schema';
import { useFieldSchema, useForm } from '@formily/react';
import { SchemaInitializer, SchemaInitializerItemType, useCollectionManager, useCollectionV2 } from '@nocobase/client';
const quickEditField = [
'attachment',
'textarea',
'markdown',
'json',
'richText',
'polygon',
'circle',
'point',
'lineString',
];
export const findTableColumn = (schema: Schema, key: string, action: string, deepth = 0) => {
return schema.reduceProperties((buf, s) => {
if (s[key] === action) {
return s;
}
const c = s.reduceProperties((buf, s) => {
if (s[key] === action) {
return s;
}
return buf;
});
if (c) {
return c;
}
return buf;
});
};
export const removeTableColumn = (schema, cb) => {
cb(schema.parent);
};
export const useTableColumnInitializerFields = () => {
const { name, fields = [] } = useCollectionV2();
const { getInterface, getCollection } = useCollectionManager();
const fieldSchema = useFieldSchema();
const isSubTable = fieldSchema['x-component'] === 'AssociationField.SubTable';
const form = useForm();
const isReadPretty = isSubTable ? form.readPretty : true;
return fields
.filter(
(field) => field?.interface && field?.interface !== 'subTable' && !field?.isForeignKey && !field?.treeChildren,
)
.map((field) => {
const interfaceConfig = getInterface(field.interface);
const isFileCollection = field?.target && getCollection(field?.target)?.template === 'file';
const schema = {
name: field.name,
'x-collection-field': `${name}.${field.name}`,
'x-component': 'CollectionField',
'x-component-props': isFileCollection
? {
fieldNames: {
label: 'preview',
value: 'id',
},
}
: {},
'x-read-pretty': isReadPretty || field.uiSchema?.['x-read-pretty'],
'x-decorator': isSubTable
? quickEditField.includes(field.interface) || isFileCollection
? 'QuickEdit'
: 'FormItem'
: null,
'x-decorator-props': {
labelStyle: {
display: 'none',
},
},
};
return {
type: 'item',
name: field.name,
title: field?.uiSchema?.title || field.name,
Component: 'TableCollectionFieldInitializer',
find: findTableColumn,
remove: removeTableColumn,
schemaInitialize: (s) => {
interfaceConfig?.schemaInitialize?.(s, {
field,
readPretty: isReadPretty,
block: 'Table',
targetCollection: getCollection(field.target),
});
},
field,
schema,
} as SchemaInitializerItemType;
});
};
export const tableColumnInitializer = new SchemaInitializer({
name: 'tableColumnInitializer',
insertPosition: 'beforeEnd',
icon: 'SettingOutlined',
title: 'Configure columns',
wrap: (s) => {
if (s['x-action-column']) {
return s;
}
return {
type: 'void',
'x-decorator': 'TableColumnDecorator',
'x-settings': 'tableColumnSettings',
'x-component': 'TableColumn',
properties: {
[s.name]: {
...s,
},
},
};
},
items: [
{
name: 'displayFields',
type: 'itemGroup',
title: '{{t("Display fields")}}',
useChildren() {
const columns = useTableColumnInitializerFields();
console.log('columns', columns);
return columns;
},
},
],
});

View File

@ -1,17 +0,0 @@
import { SchemaSettings } from '@nocobase/client';
export const tableColumnSettings = new SchemaSettings({
name: 'tableColumnSettings',
items: [
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
},
},
],
});

View File

@ -1,7 +0,0 @@
import { useField } from '@formily/react';
import React from 'react';
export const TableColumn = () => {
const field = useField();
return <div role="button">{field.title}</div>;
};

View File

@ -1,9 +0,0 @@
import { useState } from 'react';
export const useTableDecoratorProps = ({ fieldNames }) => {
const [expandFlag, setExpandFlag] = useState(fieldNames ? true : false);
return {
expandFlag,
setExpandFlag,
};
};

View File

@ -1,20 +0,0 @@
import { Plugin } from '@nocobase/client';
import { tableSettings } from './Table.settings';
import { TableToolbar } from './Table.toolbar';
import { useTableProps } from './Table.useProps';
import { useTableDecoratorProps } from './Table.decoratorProps';
import { tableColumnInitializer } from './Table.column.initializer';
import { NocoBaseTable } from './Table';
import { tableColumnSettings } from './Table.column.settings';
import { TableColumn } from './Table.column';
import { TableColumnDecorator } from './Table.column.decorator';
export class TablePlugin extends Plugin {
async load() {
this.app.schemaSettingsManager.add(tableSettings, tableColumnSettings);
this.app.addComponents({ TableToolbar, NocoBaseTable, TableColumn, TableColumnDecorator });
this.app.addScopes({ useTableProps, useTableDecoratorProps });
this.app.schemaInitializerManager.add(tableColumnInitializer);
}
}

View File

@ -1,35 +0,0 @@
import { SchemaSettings, useBlockSettingsV2 } from '@nocobase/client';
export const tableSettings = new SchemaSettings({
name: 'tableSettings',
items: [
{
name: 'bordered',
type: 'switch',
useComponentProps() {
const { dn, props } = useBlockSettingsV2<{ bordered?: boolean }>();
return {
title: 'Bordered',
checked: !!props.bordered,
onChange(v) {
dn.deepMerge({
'x-decorator-props': {
bordered: v,
},
});
},
};
},
},
{
name: 'remove',
type: 'remove',
componentProps: {
removeParentsIfNoChildren: true,
breakRemoveOn: {
'x-component': 'Grid',
},
},
},
],
});

View File

@ -1,7 +0,0 @@
import React from 'react';
import { SchemaToolbar, useCollectionV2 } from '@nocobase/client';
export const TableToolbar = () => {
const { name, title } = useCollectionV2();
return <SchemaToolbar title={title || name} draggable={false} />;
};

View File

@ -1,4 +0,0 @@
import { withSchemaComponentProps } from '@nocobase/client';
import { Table } from 'antd';
export const NocoBaseTable = withSchemaComponentProps(Table);

View File

@ -1,105 +0,0 @@
import React from 'react';
import { useDataBlockRequestV2, useBlockSettingsV2, useDesignable, useSchemaInitializerRender } from '@nocobase/client';
import { ISchema, RecursionField, Schema, useFieldSchema } from '@formily/react';
import { TableProps } from 'antd';
interface TableRequest {
data: any[];
meta: {
count: number;
page: number;
pageSize: number;
totalPage: number;
};
}
export const isCollectionFieldComponent = (schema: ISchema) => {
return schema['x-component'] === 'CollectionField';
};
export const isColumnComponent = (schema: Schema) => {
return schema['x-component']?.endsWith('.Column') > -1;
};
const useTableColumns = () => {
const schema = useFieldSchema();
const { designable } = useDesignable();
const { exists, render } = useSchemaInitializerRender(schema['x-initializer'], schema['x-initializer-props']);
const columns = schema
.reduceProperties((buf, s) => {
if (isColumnComponent(s)) {
return buf.concat([s]);
}
return buf;
}, [])
?.map((s: Schema) => {
const collectionFields = s.reduceProperties((buf, s) => {
if (isCollectionFieldComponent(s)) {
return buf.concat([s]);
}
}, []);
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : s.name;
return {
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
dataIndex,
key: s.name,
sorter: s['x-component-props']?.['sorter'],
width: 200,
...s['x-component-props'],
render: (v, record) => {
return v;
},
};
});
if (!exists) {
return columns;
}
const tableColumns = columns.concat({
title: render(),
dataIndex: 'TABLE_COLUMN_INITIALIZER',
key: 'TABLE_COLUMN_INITIALIZER',
render: designable ? () => <div style={{ minWidth: 300 }} /> : null,
});
return tableColumns;
};
export function useTableProps(): TableProps<any> {
const { data, loading } = useDataBlockRequestV2<TableRequest>();
const { props, dn } = useBlockSettingsV2<{
rowKey?: string;
params?: Record<string, any>;
bordered?: boolean;
}>();
const { rowKey, params, bordered } = props;
const columns = useTableColumns();
return {
columns,
loading,
dataSource: data?.data || [],
rowKey,
bordered,
pagination: {
pageSize: params.pageSize || 5,
current: params.page || 1,
total: data?.meta?.count,
onChange(page, pageSize) {
dn.deepMerge({
'x-decorator-props': {
params: {
pageSize,
page,
},
},
});
},
},
components: {
body: {
row: ({ children, ...props }) => {
return <tr {...props}>{children}</tr>;
},
},
},
};
}

View File

@ -1,42 +0,0 @@
import React from 'react';
import {
CardItem,
Application,
SchemaComponent,
DataBlockProviderV2,
TableCollectionFieldInitializer,
CollectionManagerProvider,
} from '@nocobase/client';
import MockAdapter from 'axios-mock-adapter';
import requestData from './requestData.json';
import schema from './schema.json';
import collections from '../collections.json';
import { TablePlugin } from './Table.plugin';
const Root = () => {
return (
<CollectionManagerProvider collections={collections as any}>
<SchemaComponent schema={schema} />
</CollectionManagerProvider>
);
};
const app = new Application({
apiClient: {
baseURL: 'http://localhost:8000/api',
},
plugins: [TablePlugin],
providers: [Root],
components: {
CardItem,
DataBlockProviderV2,
TableCollectionFieldInitializer,
},
designable: true,
});
const mock = new MockAdapter(app.apiClient.axios);
mock.onGet('users:list').reply(200, requestData);
export default app.getRootComponent();

View File

@ -1,264 +0,0 @@
{
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "TableBlockProvider",
"x-acl-action": "users:list",
"x-decorator-props": {
"collection": "users",
"resource": "users",
"action": "list",
"params": {
"pageSize": 20
},
"rowKey": "id",
"showIndex": true,
"dragSort": false,
"disableTemplate": false
},
"x-designer": "TableBlockDesigner",
"x-component": "CardItem",
"x-filter-targets": [],
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-initializer": "TableActionInitializers",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)"
}
},
"properties": {
"a94r4fylw0f": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-action": "create",
"x-acl-action": "create",
"title": "{{t('Add new')}}",
"x-designer": "Action.Designer",
"x-component": "Action",
"x-decorator": "ACLActionProvider",
"x-component-props": {
"openMode": "drawer",
"type": "primary",
"component": "CreateRecordAction",
"icon": "PlusOutlined"
},
"x-align": "right",
"x-acl-action-props": {
"skipScopeCheck": true
},
"properties": {
"drawer": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{ t(\"Add record\") }}",
"x-component": "Action.Container",
"x-component-props": {
"className": "nb-action-popup"
},
"properties": {
"tabs": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Tabs",
"x-component-props": {},
"x-initializer": "TabPaneInitializersForCreateFormBlock",
"properties": {
"tab1": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{t(\"Add new\")}}",
"x-component": "Tabs.TabPane",
"x-designer": "Tabs.Designer",
"x-component-props": {},
"properties": {
"grid": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-component": "Grid",
"x-initializer": "CreateFormBlockInitializers",
"x-uid": "rgh15rmpn51",
"x-async": false,
"x-index": 1
}
},
"x-uid": "xcmqqdez9up",
"x-async": false,
"x-index": 1
}
},
"x-uid": "b7zpfmevge2",
"x-async": false,
"x-index": 1
}
},
"x-uid": "dvp2ppiiol3",
"x-async": false,
"x-index": 1
}
},
"x-uid": "31ntpwf34wy",
"x-async": false,
"x-index": 2
}
},
"x-uid": "m58frw0kwnm",
"x-async": false,
"x-index": 1
},
"ve197icvkz4": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "array",
"x-initializer": "TableColumnInitializers",
"x-component": "TableV2",
"x-component-props": {
"rowKey": "id",
"rowSelection": {
"type": "checkbox"
},
"useProps": "{{ useTableBlockProps }}"
},
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"title": "{{ t(\"Actions\") }}",
"x-action-column": "actions",
"x-decorator": "TableV2.Column.ActionBar",
"x-component": "TableV2.Column",
"x-designer": "TableV2.ActionColumnDesigner",
"x-initializer": "TableActionColumnInitializers",
"properties": {
"actions": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "DndContext",
"x-component": "Space",
"x-component-props": {
"split": "|"
},
"x-uid": "yu3vl95pjxe",
"x-async": false,
"x-index": 1
}
},
"x-uid": "f174ti2ljhj",
"x-async": false,
"x-index": 1
},
"ptw1didvz8u": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "TableV2.Column.Decorator",
"x-designer": "TableV2.Column.Designer",
"x-component": "TableV2.Column",
"properties": {
"id": {
"_isJSONSchemaObject": true,
"version": "2.0",
"x-collection-field": "users.id",
"x-component": "CollectionField",
"x-component-props": {},
"x-read-pretty": true,
"x-decorator": null,
"x-decorator-props": {
"labelStyle": {
"display": "none"
}
},
"x-uid": "btph12il4oe",
"x-async": false,
"x-index": 1
}
},
"x-uid": "gtkofwm9ge1",
"x-async": false,
"x-index": 2
},
"ayc1c6i8lo3": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "TableV2.Column.Decorator",
"x-designer": "TableV2.Column.Designer",
"x-component": "TableV2.Column",
"properties": {
"nickname": {
"_isJSONSchemaObject": true,
"version": "2.0",
"x-collection-field": "users.nickname",
"x-component": "CollectionField",
"x-component-props": {
"ellipsis": true
},
"x-read-pretty": true,
"x-decorator": null,
"x-decorator-props": {
"labelStyle": {
"display": "none"
}
},
"x-uid": "nrc2to382kx",
"x-async": false,
"x-index": 1
}
},
"x-uid": "8ip9l7b0xfz",
"x-async": false,
"x-index": 3
},
"jg6znqow8ds": {
"_isJSONSchemaObject": true,
"version": "2.0",
"type": "void",
"x-decorator": "TableV2.Column.Decorator",
"x-designer": "TableV2.Column.Designer",
"x-component": "TableV2.Column",
"properties": {
"username": {
"_isJSONSchemaObject": true,
"version": "2.0",
"x-collection-field": "users.username",
"x-component": "CollectionField",
"x-component-props": {
"ellipsis": true
},
"x-read-pretty": true,
"x-decorator": null,
"x-decorator-props": {
"labelStyle": {
"display": "none"
}
},
"x-uid": "a4y8vhd49n2",
"x-async": false,
"x-index": 1
}
},
"x-uid": "yk1ydfk1gug",
"x-async": false,
"x-index": 4
}
},
"x-uid": "fmq2qd3uthy",
"x-async": false,
"x-index": 2
}
},
"x-uid": "mgsx7skkb50",
"x-async": false,
"x-index": 1
}

View File

@ -1,28 +0,0 @@
{
"data": [
{
"createdAt": "2023-12-04T09:42:52.953Z",
"updatedAt": "2023-12-04T09:42:52.953Z",
"appLang": null,
"createdById": null,
"email": "admin@nocobase.com",
"id": 1,
"nickname": "Super Admin",
"phone": null,
"systemSettings": {},
"updatedById": null,
"username": "nocobase"
}
],
"meta": {
"count": 1,
"page": 1,
"pageSize": 20,
"totalPage": 1,
"allowedActions": {
"view": [1],
"update": [1],
"destroy": []
}
}
}

View File

@ -1,26 +0,0 @@
{
"type": "void",
"name": "root",
"x-decorator": "DataBlockProviderV2",
"x-use-decorator-props": "useTableDecoratorProps",
"x-decorator-props": {
"collection": "users",
"action": "list",
"params": {
"pageSize": 20
},
"rowKey": "id"
},
"x-toolbar": "TableToolbar",
"x-settings": "tableSettings",
"x-component": "CardItem",
"properties": {
"ve197icvkz4": {
"type": "array",
"x-initializer": "tableColumnInitializer",
"x-component": "NocoBaseTable",
"x-use-component-props": "useTableProps",
"ptw1didvz8u": {}
}
}
}

View File

@ -95,6 +95,35 @@
"parentKey": null,
"reverseKey": null,
"unique": true
},
{
"key": "t09bauwm0wb",
"name": "roles",
"type": "belongsToMany",
"interface": "m2m",
"description": null,
"collectionName": "users",
"parentKey": null,
"reverseKey": null,
"target": "roles",
"foreignKey": "userId",
"otherKey": "roleName",
"onDelete": "CASCADE",
"sourceKey": "id",
"targetKey": "name",
"through": "rolesUsers",
"uiSchema": {
"type": "array",
"title": "{{t(\"Roles\")}}",
"x-component": "AssociationField",
"x-component-props": {
"multiple": true,
"fieldNames": {
"label": "title",
"value": "name"
}
}
}
}
]
},

View File

@ -43,8 +43,9 @@ Table 中的字段信息及列表数据,都是存储在数据库中的。
```tsx | pure
const DataBlockProvider = (props) => {
return <DataBlock.Provider value={props}>
<CollectionProvider> / <AssociationProvider>
return <DataBlockContextV2.Provider>
<CollectionDataSourceProvider>
<CollectionProvider> / <AssociationProvider>
<BlockResourceProvider>
<BlockRequestProvider>
{action !== 'list' && <RecordProvider record={blocRequest.data}>
@ -53,7 +54,8 @@ const DataBlockProvider = (props) => {
</BlockRequestProvider>
</BlockResourceProvider> / </AssociationProvider>
</CollectionProvider>
</DataBlock.Provider>
</CollectionDataSourceProvider>
</DataBlockContextV2.Provider>
}
```
@ -71,6 +73,7 @@ const DataBlockProvider = (props) => {
'x-decorator': 'DataBlockProvider',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
action: 'list',
tableProps: {
bordered: true,
@ -82,7 +85,7 @@ const DataBlockProvider = (props) => {
### 完整示例
<code src="./demos/data-block-provider/demo1.tsx"></code>
<code src="./demos/data-block-provider/complete-demo.tsx"></code>
## 属性
@ -111,6 +114,7 @@ interface AllDataBlockProps {
- collection`x-decorator-props`):区块的 collection 表名,用于获取区块的字段信息和区块数据
- association`x-decorator-props`):区块的关系字段名,用于获取区块的关系字段信息和关系字段数据
- dataSource(`x-decorator-props`): 数据源,具体可参考 [Data Modeling](https://docs.nocobase.com/manual/data-modeling)
- action`x-decorator-props`):区块的请求类型,`list` 或 `get`
- params`x-decorator-props` 和 `x-use-decorator-props`):区块的请求参数,同时存在于
- filterByTk`x-use-decorator-props`):相当于 `params.filterByTk`,可理解为 `id`,用于获取单条数据
@ -120,16 +124,18 @@ interface AllDataBlockProps {
```tsx | pure
const DataBlockProvider = (props) => {
return <DataBlock.Provider value={props}>
<CollectionProvider name={props.collection}> / <CollectionProvider name={props.association}>
<BlockResourceProvider {...props}>
<BlockRequestProvider resource={resource}>
{action !== 'list' && <RecordProvider record={blocRequest.data}>
{props.children}
</Record>}
</BlockRequestProvider>
</BlockResourceProvider>
</CollectionProvider>
return <DataBlockContextV2.Provider value={props}>
<CollectionDataSourceProvider>
<CollectionProvider name={props.collection}> / <CollectionProvider name={props.association}>
<BlockResourceProvider {...props}>
<BlockRequestProvider resource={resource}>
{action !== 'list' && <RecordProvider record={blocRequest.data}>
{props.children}
</Record>}
</BlockRequestProvider>
</BlockResourceProvider>
</CollectionProvider>
</CollectionDataSourceProvider>
</DataBlock.Provider>
}
```
@ -212,18 +218,29 @@ const checked = props.tableProps.bordered;
<code src="./demos/data-block-provider/collection-table-list.tsx"></code>
#### Form get
#### Form get & update
<code src="./demos/data-block-provider/collection-form-get-and-update.tsx"></code>
#### Form create
#### Form record
<code src="./demos/data-block-provider/collection-form-create.tsx"></code>
#### Form record & update
<code src="./demos/data-block-provider/collection-form-record-and-update.tsx"></code>
### association
#### Table list
association 与 collection 类似,只是需要提供 `sourceId`,我们以 `Table list` 为例。
#### Form get
#### Table list & sourceId
#### Form create
<code src="./demos/data-block-provider/association-table-list-and-source-id.tsx"></code>
#### Table list & parentRecord
如果不提供 `sourceId`,则需要提供 `parentRecord`,我们以 `Table list` 为例。
<code src="./demos/data-block-provider/association-table-list-and-parent-record.tsx"></code>
#### Form record

View File

@ -0,0 +1,98 @@
import React from 'react';
import { Select, Table, TableProps } from 'antd';
import { SchemaComponent, UseDataBlockProps, useDataBlockRequestV2, withDynamicSchemaProps } from '@nocobase/client';
import { ISchema } from '@formily/json-schema';
import { createApp } from '../../../collection/demos/createApp';
import useUrlState from '@ahooksjs/use-url-state';
const collection = 'users';
const associationField = 'roles';
const association = `${collection}.${associationField}`;
const action = 'list';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProviderV2',
'x-use-decorator-props': 'useBlockDecoratorProps',
'x-decorator-props': {
association,
action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps',
},
},
};
const MyTable = withDynamicSchemaProps(Table);
function useTableProps(): TableProps<any> {
const { data, loading } = useDataBlockRequestV2<any[]>();
return {
loading,
dataSource: data?.data || [],
columns: [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Title',
dataIndex: 'title',
},
{
title: 'Description',
dataIndex: 'description',
},
],
};
}
const useBlockDecoratorProps: UseDataBlockProps<'CollectionList'> = () => {
const parentRecord = {
id: 1,
username: 'Tom',
};
return {
parentRecord,
};
};
const Demo = () => {
return <SchemaComponent schema={schema}></SchemaComponent>;
};
const mocks = {
[`${collection}/1/${associationField}:${action}`]: {
data: [
{
name: 'admin',
title: 'Admin',
description: 'Admin description',
},
{
name: 'developer',
title: 'Developer',
description: 'Developer description',
},
],
},
};
const Root = createApp(
Demo,
{
components: { MyTable },
scopes: { useTableProps, useBlockDecoratorProps },
},
mocks,
);
export default Root;

View File

@ -0,0 +1,132 @@
import React from 'react';
import { Select, Table, TableProps } from 'antd';
import { SchemaComponent, UseDataBlockProps, useDataBlockRequestV2, withDynamicSchemaProps } from '@nocobase/client';
import { ISchema } from '@formily/json-schema';
import { createApp } from '../../../collection/demos/createApp';
import useUrlState from '@ahooksjs/use-url-state';
const collection = 'users';
const associationField = 'roles';
const association = `${collection}.${associationField}`;
const action = 'list';
const schema: ISchema = {
type: 'void',
name: 'root',
'x-decorator': 'DataBlockProviderV2',
'x-use-decorator-props': 'useBlockDecoratorProps',
'x-decorator-props': {
association,
action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'array',
'x-component': 'MyTable',
'x-use-component-props': 'useTableProps',
},
},
};
const MyTable = withDynamicSchemaProps(Table);
function useTableProps(): TableProps<any> {
const { data, loading } = useDataBlockRequestV2<any[]>();
return {
loading,
dataSource: data?.data || [],
columns: [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Title',
dataIndex: 'title',
},
{
title: 'Description',
dataIndex: 'description',
},
],
};
}
const useBlockDecoratorProps: UseDataBlockProps<'CollectionList'> = () => {
const [state] = useUrlState({ userId: '1' });
return {
sourceId: state.userId,
};
};
const Demo = () => {
const [state, setState] = useUrlState({ userId: '1' });
return (
<>
<Select
defaultValue={state.userId}
options={[
{ key: 1, value: '1', label: 'Tom' },
{ key: 2, value: '2', label: 'Jack' },
]}
onChange={(v) => {
setState({ userId: v });
}}
></Select>
<SchemaComponent schema={schema}></SchemaComponent>
</>
);
};
const mocks = {
[`${collection}/1/${associationField}:${action}`]: {
data: [
{
name: 'admin',
title: 'Admin',
description: 'Admin description',
},
{
name: 'developer',
title: 'Developer',
description: 'Developer description',
},
],
},
[`${collection}/2/${associationField}:${action}`]: {
data: [
{
name: 'developer',
title: 'Developer',
description: 'Developer description',
},
{
name: 'tester',
title: 'Tester',
description: 'Tester description',
},
],
},
[`${collection}:get/1`]: {
id: 1,
username: 'Tom',
},
[`${collection}:get/2`]: {
id: 1,
username: 'Jack',
},
};
const Root = createApp(
Demo,
{
components: { MyTable },
scopes: { useTableProps, useBlockDecoratorProps },
},
mocks,
);
export default Root;

View File

@ -1,8 +1,9 @@
import React, { FC } from 'react';
import { SchemaComponent, useDataBlockResourceV2, withSchemaComponentProps } from '@nocobase/client';
import { createApp } from './createApp';
import { Button, Form, Input, InputNumber } from 'antd';
import { FormProps } from 'antd/lib';
import { Button, Form, FormProps, Input, InputNumber, notification } from 'antd';
import { SchemaComponent, useDataBlockResourceV2, withDynamicSchemaProps } from '@nocobase/client';
import { ISchema } from '@formily/json-schema';
import { createApp } from '../../../collection/demos/createApp';
interface DemoFormFieldType {
id: number;
@ -10,7 +11,7 @@ interface DemoFormFieldType {
age: number;
}
type DemoFormProps = FormProps<DemoFormFieldType>;
const DemoForm: FC<DemoFormProps> = withSchemaComponentProps((props) => {
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
return (
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
<Form.Item<DemoFormFieldType>
@ -38,24 +39,38 @@ const DemoForm: FC<DemoFormProps> = withSchemaComponentProps((props) => {
function useDemoFormProps(): DemoFormProps {
const resource = useDataBlockResourceV2();
const onFinish = async (values: DemoFormFieldType) => {
console.log('values', values);
await resource.create({
values,
});
notification.success({
message: 'Save successfully!',
});
};
return {
onFinish: (values) => {
resource.create({ values });
},
onFinish,
};
}
const collection = 'users';
const schema = {
const schema: ISchema = {
type: 'void',
name: 'hello',
name: 'root',
'x-decorator': 'DataBlockProviderV2',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
'x-decorator-props': {
collection: collection,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'object',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
},
},
};
const Demo = () => {
@ -64,11 +79,17 @@ const Demo = () => {
const mocks = {
[`${collection}:create`]: (config) => {
console.log('请求结果', config.data);
console.log('config.data', config.data);
return [200, { msg: 'ok' }];
},
};
const Root = createApp(Demo, { components: { DemoForm }, scopes: { useDemoFormProps } }, mocks);
const Root = createApp(
Demo,
{
components: { DemoForm },
scopes: { useDemoFormProps },
},
mocks,
);
export default Root;

View File

@ -1,15 +1,16 @@
import React, { FC, useEffect, useState } from 'react';
import React, { FC, useEffect } from 'react';
import { Button, Form, FormProps, Input, InputNumber, Select, notification } from 'antd';
import {
RecordProviderV2,
SchemaComponent,
useDataBlockRequestV2,
withSchemaComponentProps,
UseDataBlockProps,
useDataBlockResourceV2,
useRecordDataV2,
withDynamicSchemaProps,
} from '@nocobase/client';
import { createApp } from './createApp';
import { Button, Form, Input, InputNumber, Select } from 'antd';
import { FormProps } from 'antd/lib';
import { ISchema } from '@formily/json-schema';
import useUrlState from '@ahooksjs/use-url-state';
import { createApp } from '../../../collection/demos/createApp';
interface DemoFormFieldType {
id: number;
@ -17,7 +18,7 @@ interface DemoFormFieldType {
age: number;
}
type DemoFormProps = FormProps<DemoFormFieldType>;
const DemoForm: FC<DemoFormProps> = withSchemaComponentProps((props) => {
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
return (
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
<Form.Item<DemoFormFieldType>
@ -45,56 +46,78 @@ const DemoForm: FC<DemoFormProps> = withSchemaComponentProps((props) => {
function useDemoFormProps(): DemoFormProps {
const data = useRecordDataV2<DemoFormFieldType>();
const resource = useDataBlockResourceV2();
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(data);
}, [data, form]);
const onFinish = async (values: DemoFormFieldType) => {
console.log('values', values);
await resource.update({
filterByTk: data.id,
values,
});
notification.success({
message: 'Save successfully!',
});
};
return {
initialValues: data,
preserve: true,
onFinish,
form,
};
}
const useFormBlockDecoratorProps: UseDataBlockProps<'CollectionGet'> = () => {
const { filterByTk } = useRecordDataV2<{ filterByTk: number }>();
const useBlockDecoratorProps: UseDataBlockProps<'CollectionGet'> = () => {
const [state] = useUrlState({ id: '1' });
return {
filterByTk,
filterByTk: state.id,
};
};
const collection = 'users';
const action = 'get';
const schema = {
const schema: ISchema = {
type: 'void',
name: 'hello',
name: 'root',
'x-decorator': 'DataBlockProviderV2',
'x-use-decorator-props': 'useFormBlockDecoratorProps',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
'x-use-decorator-props': 'useBlockDecoratorProps',
'x-decorator-props': {
collection: collection,
action: action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'object',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
},
},
};
const Demo = () => {
const [id, setId] = useState(1);
const [state, setState] = useUrlState({ id: '1' });
return (
<RecordProviderV2 record={{ filterByTk: id }}>
<>
<Select
defaultValue={id}
defaultValue={state.id}
options={[
{ key: 1, value: 1, label: 'Bamboo' },
{ key: 2, value: 2, label: 'Mary' },
{ key: 1, value: '1', label: 'Bamboo' },
{ key: 2, value: '2', label: 'Mary' },
]}
onChange={(v) => {
setId(v);
setState({ id: v });
}}
></Select>
<SchemaComponent schema={schema}></SchemaComponent>
</RecordProviderV2>
</>
);
};
@ -103,7 +126,7 @@ const mocks = {
const { filterByTk } = config.params;
return {
data:
filterByTk === 1
Number(filterByTk) === 1
? {
id: 1,
username: 'Bamboo',
@ -116,11 +139,19 @@ const mocks = {
},
};
},
[`${collection}:update`]: function (config) {
console.log('config.data', config.data);
return {
data: 'ok',
};
},
};
const Root = createApp(
Demo,
{ components: { DemoForm }, scopes: { useDemoFormProps, useFormBlockDecoratorProps } },
{
components: { DemoForm },
scopes: { useDemoFormProps, useBlockDecoratorProps },
},
mocks,
);

View File

@ -1,15 +1,16 @@
import React, { FC, useEffect } from 'react';
import { Button, Form, FormProps, Input, InputNumber, notification } from 'antd';
import {
RecordProviderV2,
SchemaComponent,
UseDataBlockProps,
useDataBlockResourceV2,
useRecordDataV2,
useRecordV2,
withSchemaComponentProps,
withDynamicSchemaProps,
} from '@nocobase/client';
import { Button, Form, Input, InputNumber } from 'antd';
import { FormProps } from 'antd/lib';
import React, { FC, useEffect } from 'react';
import { createApp } from './createApp';
import { ISchema } from '@formily/json-schema';
import { createApp } from '../../../collection/demos/createApp';
interface DemoFormFieldType {
id: number;
@ -17,7 +18,7 @@ interface DemoFormFieldType {
age: number;
}
type DemoFormProps = FormProps<DemoFormFieldType>;
const DemoForm: FC<DemoFormProps> = withSchemaComponentProps((props) => {
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
return (
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
<Form.Item<DemoFormFieldType>
@ -45,51 +46,92 @@ const DemoForm: FC<DemoFormProps> = withSchemaComponentProps((props) => {
function useDemoFormProps(): DemoFormProps {
const data = useRecordDataV2<DemoFormFieldType>();
const resource = useDataBlockResourceV2();
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(data);
}, [data, form]);
const onFinish = async (values: DemoFormFieldType) => {
console.log('values', values);
await resource.update({
filterByTk: data.id,
values,
});
notification.success({
message: 'Save successfully!',
});
};
return {
initialValues: data,
preserve: true,
onFinish,
form,
};
}
const useFormBlockDecoratorProps: UseDataBlockProps<'CollectionRecord'> = () => {
const record = useRecordV2();
const record = useRecordDataV2();
return {
record,
};
};
const collection = 'users';
const action = 'get';
const schema = {
const schema: ISchema = {
type: 'void',
name: 'hello',
name: 'root',
'x-decorator': 'DataBlockProviderV2',
'x-use-decorator-props': 'useFormBlockDecoratorProps',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
'x-decorator-props': {
collection: collection,
action: action,
},
'x-component': 'CardItem',
properties: {
demo: {
type: 'object',
'x-component': 'DemoForm',
'x-use-component-props': 'useDemoFormProps',
},
},
};
const recordData = {
id: 1,
username: 'Bamboo',
age: 18,
};
const Demo = () => {
return (
<RecordProviderV2
record={{
id: 1,
username: 'Bamboo',
age: 18,
}}
>
<SchemaComponent schema={schema}></SchemaComponent>
</RecordProviderV2>
<>
<RecordProviderV2 record={recordData}>
<SchemaComponent schema={schema}></SchemaComponent>
</RecordProviderV2>
</>
);
};
const Root = createApp(Demo, { components: { DemoForm }, scopes: { useDemoFormProps, useFormBlockDecoratorProps } });
const mocks = {
[`${collection}:update`]: function (config) {
console.log('config.data', config.data);
return {
data: 'ok',
};
},
};
const Root = createApp(
Demo,
{
components: { DemoForm },
scopes: { useDemoFormProps, useFormBlockDecoratorProps },
},
mocks,
);
export default Root;

View File

@ -221,7 +221,9 @@ export class CollectionV2 {
}
getField(name: SchemaKey) {
const fieldsMap = this.getFieldsMap();
if (typeof name === 'string' && name.startsWith(`${this.name}.`)) {
name = name.replace(`${this.name}.`, '');
}
if (String(name).split('.').length > 1) {
const [fieldName, ...others] = String(name).split('.');
const field = fieldsMap[fieldName];

View File

@ -61,7 +61,7 @@ export function useRecordV2<DataType = {}, ParentDataType = {}>(
return context;
}
export function useRecordDataV2<DataType>(showErrorWhenNotExists = true): DataType {
export function useRecordDataV2<DataType = any>(showErrorWhenNotExists = true): DataType {
const record = useRecordV2<DataType>(showErrorWhenNotExists);
return record.data;
}

View File

@ -12,7 +12,7 @@ export interface AllDataBlockProps {
collection: string;
association: string;
dataSource?: string;
sourceId: string | number;
sourceId?: string | number;
filterByTk: string | number;
record: RecordV2;
action?: 'list' | 'get';

View File

@ -1,5 +1,5 @@
import { useDeepCompareEffect } from 'ahooks';
import React, { FC, createContext, useContext } from 'react';
import React, { FC, createContext, useContext, useEffect } from 'react';
import { UseRequestResult, useAPIClient, useRequest } from '../../api-client';
import { useDataBlockResourceV2 } from './DataBlockResourceProvider';
@ -35,25 +35,33 @@ function useCurrentRequest<T>(options: Omit<AllDataBlockProps, 'type'>) {
// 因为修改 Schema 会导致 params 对象发生变化,所以这里使用 `DeepCompare`
useDeepCompareEffect(() => {
request.run();
if (action) {
request.run();
}
}, [params, action, record]);
useEffect(() => {
if (action) {
request.run();
}
}, [resource]);
return request;
}
function useParentRequest<T>(options: Omit<AllDataBlockProps, 'type'>) {
const { sourceId, association, parentRecord } = options;
const api = useAPIClient();
return useRequest<T>(
() => {
async () => {
if (parentRecord) return Promise.resolve({ data: parentRecord });
if (!association) return Promise.resolve({ data: undefined });
// "association": "Collection.Field"
const arr = association.split('.');
// <collection>:get/<filterByTk>
const url = `${arr[0]}:get/${sourceId}`;
return api.request({ url }).then((res) => res.data);
const res = await api.request({ url });
return res.data;
},
{
refreshDeps: [association, parentRecord, sourceId],

View File

@ -3,7 +3,7 @@ import { IResource } from '@nocobase/sdk';
import { useAPIClient } from '../../api-client';
import { useDataBlockPropsV2 } from './DataBlockProvider';
import { DEFAULT_DATA_SOURCE_NAME, useCollectionManagerV2 } from '../collection';
import { DEFAULT_DATA_SOURCE_NAME, RecordV2, useCollectionManagerV2 } from '../collection';
export const DataBlockResourceContextV2 = createContext<IResource>(null);
DataBlockResourceContextV2.displayName = 'DataBlockResourceContextV2';
@ -26,7 +26,8 @@ export const DataBlockResourceProviderV2: FC<{ children?: ReactNode }> = ({ chil
if (association && parentRecord) {
const associationCollection = cm.getCollection(association);
if (associationCollection) {
return parentRecord.data[associationCollection.sourceKey || 'id'];
const parentRecordData = parentRecord instanceof RecordV2 ? parentRecord.data : parentRecord;
return parentRecordData[associationCollection.sourceKey || 'id'];
}
}
}, [sourceId, parentRecord]);