fix: add field groups

This commit is contained in:
dream2023 2024-01-11 18:16:08 +08:00
parent f934cb9bbe
commit 9a85755684
13 changed files with 174 additions and 266 deletions

View File

@ -8,7 +8,7 @@
interface CollectionManagerOptionsV2 {
collections?: Record<string, CollectionV2[] | CollectionOptions[]> | CollectionV2[] | CollectionOptions[];
collectionTemplates?: CollectionTemplateV2[] | CollectionTemplateOptions[];
collectionFieldInterfaces?: CollectionFieldInterfaceV2[] | CollectionFieldInterfaceOptions[];
fieldInterfaces?: CollectionFieldInterfaceV2[] | CollectionFieldInterfaceOptions[];
collectionNamespaces?: Record<string, string>;
}
@ -34,7 +34,6 @@ class CollectionManagerV2 {
getCollections(ns?: string, predicate?: (collection: CollectionV2) => boolean): CollectionV2[]
getCollection(path: string, options?: GetCollectionOptions): Promise<CollectionV2 | undefined>
getCollectionName(path: string, options?: GetCollectionOptions): Promise<string | undefined>;
removeCollection(path: string, options?: GetCollectionOptions): void;
getCollectionField(path: string, options?: GetCollectionOptions): Promise<CollectionFieldOptions | undefined>;
addFieldInterfaces(interfaces:CollectionFieldInterfaceV2[] | CollectionFieldInterfaceOptions[]): void;
@ -80,14 +79,14 @@ const collections = collectionManager.getCollections('db2');
interface CollectionManagerOptionsV2 {
collections?: Record<string, CollectionV2[] | CollectionOptions[]> | CollectionV2[] | CollectionOptions[];
collectionTemplates?: CollectionTemplateV2[] | CollectionTemplateOptions[];
collectionFieldInterfaces?: CollectionFieldInterfaceV2[] | CollectionFieldInterfaceOptions[];
fieldInterfaces?: CollectionFieldInterfaceV2[] | CollectionFieldInterfaceOptions[];
collectionNamespaces?: Record<string, string>;
}
```
- 参数说明
1. `collections`、`collectionTemplates`、`collectionFieldInterfaces` 既支持对应的实例,也支持构造函数参数。例如:
1. `collections`、`collectionTemplates`、`fieldInterfaces` 既支持对应的实例,也支持构造函数参数。例如:
```tsx | pure
const userCollectionOptions = {
@ -117,7 +116,7 @@ const checkboxCollectionFieldInterfaceOptions = {
const collectionManager1 = new CollectionManagerV2({
collections: [userCollectionOptions],
collectionTemplates: [treeCollectionTemplateOptions],
collectionFieldInterfaces: [checkboxCollectionFieldInterfaceOptions],
fieldInterfaces: [checkboxCollectionFieldInterfaceOptions],
collectionNamespaces: {
"db2": "DB2"
}
@ -131,7 +130,7 @@ const checkboxCollectionFieldInterface = new CollectionFieldInterface(checkboxCo
const collectionManager2 = new CollectionManagerV2({
collections: [userCollection],
collectionTemplates: [treeCollectionTemplate],
collectionFieldInterfaces: [checkboxCollectionFieldInterface],
fieldInterfaces: [checkboxCollectionFieldInterface],
collectionNamespaces: {
"db2": "DB2"
}
@ -453,23 +452,6 @@ collectionManager.getCollectionName('users'); // 'users'
collectionManager.getCollectionName('users.profileId'); // 'profiles'
```
### cm.removeCollection(path, options?)
移除数据表。
- 类型
```tsx | pure
class CollectionManagerV2 {
removeCollection(path: string, options?: GetCollectionOptions): void;
}
```
- 示例
```tsx | pure
collectionManager.removeCollection('users');
```
### cm.getCollectionField(path, options?)

View File

@ -3,6 +3,7 @@ import {
ApplicationOptions,
CardItem,
CollectionManagerOptionsV2,
CollectionPlugin,
DataBlockProviderV2,
InheritanceCollectionMixin,
} from '@nocobase/client';
@ -55,7 +56,6 @@ export function createApp(
) {
const collectionManager = {
collections: collections as any,
collectionMixins: [InheritanceCollectionMixin],
...(options.collectionManager as CollectionManagerOptionsV2),
};
const app = new Application({
@ -70,6 +70,7 @@ export function createApp(
DataBlockProviderV2,
CardItem,
},
plugins: [CollectionPlugin],
designable: true,
});

View File

@ -3,8 +3,8 @@ import {
ApplicationOptions,
CardItem,
CollectionManagerOptionsV2,
CollectionPlugin,
DataBlockProviderV2,
InheritanceCollectionMixin,
} from '@nocobase/client';
import MockAdapter from 'axios-mock-adapter';
import { ComponentType } from 'react';
@ -55,7 +55,6 @@ export function createApp(
) {
const collectionManager = {
collections: collections as any,
collectionMixins: [InheritanceCollectionMixin],
...(options.collectionManager as CollectionManagerOptionsV2),
};
const app = new Application({
@ -70,6 +69,7 @@ export function createApp(
DataBlockProviderV2,
CardItem,
},
plugins: [CollectionPlugin],
designable: true,
});

View File

@ -52,7 +52,8 @@ export interface GetCollectionOptions {
export interface CollectionManagerOptionsV2 {
collections?: Record<string, CollectionOptionsV2[]> | CollectionOptionsV2[];
collectionTemplates?: (typeof CollectionTemplateV2)[];
collectionFieldInterfaces?: (typeof CollectionFieldInterfaceV2)[];
fieldInterfaces?: (typeof CollectionFieldInterfaceV2)[];
fieldGroups?: Record<string, { label: string; order?: number }>;
collectionNamespaces?: Record<string, string>;
collectionMixins?: CollectionMixinConstructor[];
}
@ -66,6 +67,7 @@ export class CollectionManagerV2<Mixins = {}> {
protected collectionNamespaces: Record<string, string> = {
[DEFAULT_COLLECTION_NAMESPACE_NAME]: DEFAULT_COLLECTION_NAMESPACE_TITLE,
};
protected collectionFieldGroups: Record<string, { label: string; order?: number }> = {};
protected reloadFns?: {
[key: string]: (...args: any[]) => Promise<any>;
} = {};
@ -84,7 +86,8 @@ export class CollectionManagerV2<Mixins = {}> {
init(options: CollectionManagerOptionsV2) {
this.collectionMixins.push(...(options.collectionMixins || []));
this.addCollectionTemplates(options.collectionTemplates || []);
this.addFieldInterfaces(options.collectionFieldInterfaces || []);
this.addFieldInterfaces(options.fieldInterfaces || []);
this.addFieldGroups(options.fieldGroups || {});
this.addCollectionNamespaces(options.collectionNamespaces || {});
if (Array.isArray(options.collections)) {
this.addCollections(options.collections);
@ -105,7 +108,8 @@ export class CollectionManagerV2<Mixins = {}> {
addCollectionMixins(mixins: CollectionMixinConstructor[]) {
if (mixins.length === 0) return;
this.collectionMixins.push(...mixins);
const newMixins = mixins.filter((mixin) => !this.collectionMixins.includes(mixin));
this.collectionMixins.push(...newMixins);
// 重新添加数据表
Object.keys(this.collections).forEach((ns) => {
@ -150,7 +154,7 @@ export class CollectionManagerV2<Mixins = {}> {
}
getCollections(predicate?: (collection: CollectionV2) => boolean, options: GetCollectionOptions = {}) {
const { ns = DEFAULT_COLLECTION_NAMESPACE_NAME } = options;
if (!predicate && this.collectionArr[ns]) {
if (!predicate && this.collectionArr[ns]?.length) {
return this.collectionArr[ns];
}
@ -169,7 +173,7 @@ export class CollectionManagerV2<Mixins = {}> {
*/
getCollection(path: string, options: GetCollectionOptions = {}): (Mixins & CollectionV2) | undefined {
const { ns = DEFAULT_COLLECTION_NAMESPACE_NAME } = options;
if (!path) return undefined;
if (!path || typeof path !== 'string') return undefined;
this.checkNamespace(ns);
if (path.split('.').length > 1) {
// 获取到关联字段
@ -183,17 +187,6 @@ export class CollectionManagerV2<Mixins = {}> {
const res = this.getCollection(path, options);
return res?.name;
}
removeCollection(path: string, options: GetCollectionOptions = {}) {
const { ns = DEFAULT_COLLECTION_NAMESPACE_NAME } = options;
this.checkNamespace(ns);
if (path.split('.').length > 1) {
// 获取到关联字段
const associationField = this.getCollectionField(path);
return this.removeCollection(associationField.target, { ns });
}
delete this.collections[ns]?.[path];
}
getCollectionFields(collectionName: string, options: GetCollectionOptions = {}): CollectionFieldOptionsV2[] {
return this.getCollection(collectionName, options)?.getFields() || [];
@ -205,9 +198,10 @@ export class CollectionManagerV2<Mixins = {}> {
* getCollection('a.b.c'); // 获取 a 表的 b 字段的关联表,然后 b.target 表对应的 c 字段
*/
getCollectionField(path: SchemaKey, options: GetCollectionOptions = {}): CollectionFieldOptionsV2 | undefined {
const arr = String(path).split('.');
if (arr.length < 2) {
throw new Error(`[@nocobase/client]: CollectionManager.getCollectionField() path "${path}" is invalid`);
if (!path) return;
if (typeof path === 'object' || String(path).split('.').length < 2) {
console.error(`[@nocobase/client]: CollectionManager.getCollectionField() path "${path}" is invalid`);
return;
}
const [collectionName, ...fieldNames] = String(path).split('.');
const { ns = DEFAULT_COLLECTION_NAMESPACE_NAME } = options || {};
@ -286,6 +280,16 @@ export class CollectionManagerV2<Mixins = {}> {
return this.fieldInterfaceInstances[name];
}
addFieldGroups(fieldGroups: Record<string, { label: string; order?: number }>) {
Object.assign(this.collectionFieldGroups, fieldGroups);
}
getFieldGroups() {
return this.collectionFieldGroups;
}
getFieldGroup(name: string) {
return this.collectionFieldGroups[name];
}
setReloadFn(fn: (...args: any[]) => Promise<any>, namespace: string = DEFAULT_COLLECTION_NAMESPACE_NAME) {
this.reloadFns[namespace] = fn;
}

View File

@ -3,29 +3,15 @@ import { useAPIClient, useRequest } from '../api-client';
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
import { CollectionCategroriesContext } from './context';
import { CollectionManagerOptions } from './types';
import { CollectionManagerProviderV2, CollectionManagerV2, useApp, useCollectionManagerV2 } from '../application';
import { CollectionManagerProviderV2, useCollectionManagerV2 } from '../application';
import { useCollectionHistory } from './CollectionHistoryProvider';
import { useAppSpin } from '../application/hooks/useAppSpin';
import { InheritanceCollectionMixin } from './mixins/InheritanceCollectionMixin';
import { interfaces as defaultInterfaces } from './Configuration/interfaces';
// import { general, expression, sql, tree, view } from './templates';
import { collectionTemplates } from './Configuration/templates';
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
const { reloadCallback, cm, collections = [] } = props;
const cmContext = useCollectionManagerV2();
const app = useApp();
const newCm = useMemo(() => {
let ctx = cm || cmContext;
if (!ctx)
ctx = new CollectionManagerV2(
{
collectionMixins: [InheritanceCollectionMixin],
collectionFieldInterfaces: defaultInterfaces as any,
collectionTemplates: Object.values(collectionTemplates) as any,
},
app,
);
const ctx = cm || cmContext;
return ctx.inherit({
collections: collections as any,
reloadCallback,

View File

@ -4,7 +4,6 @@ export * from './AddFieldAction';
export * from './ConfigurationTable';
export * from './EditFieldAction';
export * from './components';
export * from './templates';
export * from './CollectionFieldsTable';
export * from './schemas/collections';
export * from './OverridingCollectionField';

View File

@ -1,68 +1,24 @@
import { ISchema } from '@formily/react';
import set from 'lodash/set';
import * as types from '../interfaces';
import { CollectionFieldInterfaceV2, useCollectionManagerV2 } from '../../application';
import { useMemo } from 'react';
export const interfaces = new Map<string, ISchema>();
const fields = {};
const groups: Record<
string,
{
label: string;
order: number;
}
> = {};
export function registerField(group: string, type: string, schema) {
fields[group] = fields[group] || {};
set(fields, [group, type], schema);
interfaces.set(type, schema);
}
export function registerGroup(key: string, label: string | { label: string; order?: number }) {
const group = typeof label === 'string' ? { label } : label;
if (!group.order) {
group.order = (Object.keys(groups).length + 1) * 10;
}
groups[key] = group as Required<typeof group>;
}
/**
* @deprecated
*/
export const registerGroupLabel = registerGroup;
Object.keys(types).forEach((type) => {
const schema = types[type];
registerField(schema.group || 'others', type, { order: 0, ...schema });
});
registerGroup('basic', '{{t("Basic")}}');
registerGroup('choices', '{{t("Choices")}}');
registerGroup('media', '{{t("Media")}}');
registerGroup('datetime', '{{t("Date & Time")}}');
registerGroup('relation', '{{t("Relation")}}');
registerGroup('advanced', '{{t("Advanced type")}}');
registerGroup('systemInfo', '{{t("System info")}}');
registerGroup('others', '{{t("Others")}}');
export const getOptions = (collectionFieldInterfaces: Record<string, CollectionFieldInterfaceV2[]>) => {
return Object.keys(groups)
export const getOptions = (
fieldInterfaces: Record<string, CollectionFieldInterfaceV2[]>,
fieldGroups: Record<string, { label: string; order?: number }>,
) => {
return Object.keys(fieldGroups)
.map((groupName) => {
const group = groups[groupName];
const group = fieldGroups[groupName];
return {
...group,
key: groupName,
children: Object.keys(collectionFieldInterfaces[groupName] || {})
children: Object.keys(fieldInterfaces[groupName] || {})
.map((type) => {
const field = collectionFieldInterfaces[groupName][type];
const field = fieldInterfaces[groupName][type];
return {
value: type,
label: field.title,
name: type,
...collectionFieldInterfaces[groupName][type],
...fieldInterfaces[groupName][type],
};
})
.sort((a, b) => a.order - b.order),
@ -76,17 +32,17 @@ export const useFieldInterfaceOptions = () => {
return useMemo(() => {
const fieldInterfaceInstances = cm.getFieldInterfaces();
const groups = Object.values(fieldInterfaceInstances).reduce<Record<string, CollectionFieldInterfaceV2[]>>(
(memo, fieldInterface) => {
const group = fieldInterface.group || 'basic';
if (!memo[group]) {
memo[group] = [];
}
memo[group].push(fieldInterface);
return memo;
},
{},
);
return getOptions(groups);
const fieldGroups = cm.getFieldGroups();
const fieldInterfaceInstancesByGroups = Object.values(fieldInterfaceInstances).reduce<
Record<string, CollectionFieldInterfaceV2[]>
>((memo, fieldInterface) => {
const group = fieldInterface.group || 'basic';
if (!memo[group]) {
memo[group] = [];
}
memo[group].push(fieldInterface);
return memo;
}, {});
return getOptions(fieldInterfaceInstancesByGroups, fieldGroups);
}, [cm]);
};

View File

@ -1,15 +0,0 @@
import { ISchema } from '@formily/react';
import * as types from '../templates';
export const templates = new Map<string, ISchema>();
export const collectionTemplates: any = {};
export function registerTemplate(key: string, schema: any) {
collectionTemplates[key] = schema;
}
Object.keys(types).forEach((type) => {
const schema = types[type];
registerTemplate(schema.name || 'others', { order: 0, ...schema });
});

View File

@ -1,102 +1,127 @@
import { Plugin } from '../application/Plugin';
import { InheritanceCollectionMixin } from './mixins/InheritanceCollectionMixin';
// import {
// checkbox,
// checkboxGroup,
// chinaRegion,
// collection,
// color,
// createdAt,
// createdBy,
// datetime,
// email,
// icon,
// id,
// input,
// integer,
// json,
// linkTo,
// m2m,
// m2o,
// markdown,
// multipleSelect,
// number,
// o2m,
// o2o,
// password,
// percent,
// phone,
// radioGroup,
// richText,
// select,
// subTable,
// tableoid,
// textarea,
// time,
// updatedAt,
// updatedBy,
// url,
// } from './interfaces';
// import { general, expression, sql, tree, view } from './templates';
import { interfaces } from './Configuration/interfaces';
import { collectionTemplates } from './Configuration/templates';
import { collection } from './Configuration/schemas/collections';
import {
checkbox,
checkboxGroup,
chinaRegion,
collection,
color,
createdAt,
createdBy,
datetime,
email,
icon,
id,
input,
integer,
json,
linkTo,
m2m,
m2o,
markdown,
multipleSelect,
number,
o2m,
o2o,
password,
percent,
phone,
radioGroup,
richText,
select,
subTable,
tableoid,
textarea,
time,
updatedAt,
updatedBy,
url,
} from './interfaces';
import { general, expression, sql, tree, view } from './templates';
import { collection as collectionData } from './Configuration/schemas/collections';
export class CollectionPlugin extends Plugin {
async load() {
this.collectionManager.addCollectionMixins([InheritanceCollectionMixin]);
this.addFieldInterfaces();
this.addCollectionTemplates();
this.addFieldInterfaces();
this.collectionManager.setReloadFn(this.reloadCollections.bind(this));
// await this.collectionManager.reload();
}
addFieldGroups() {
this.collectionManager.addFieldGroups({
basic: {
label: '{{t("Basic")}}',
},
choices: {
label: '{{t("Choices")}}',
},
media: {
label: '{{t("Media")}}',
},
datetime: {
label: '{{t("Date & Time")}}',
},
relation: {
label: '{{t("Relation")}}',
},
advanced: {
label: '{{t("Advanced type")}}',
},
systemInfo: {
label: '{{t("System info")}}',
},
others: {
label: '{{t("Others")}}',
},
});
}
addFieldInterfaces() {
this.app.collectionManager.addFieldInterfaces([...interfaces.values()]);
// this.app.collectionManager.addFieldInterfaces([
// checkbox,
// checkboxGroup,
// chinaRegion,
// collection,
// color,
// createdAt,
// createdBy,
// datetime,
// email,
// icon,
// id,
// input,
// integer,
// json,
// linkTo,
// m2m,
// m2o,
// markdown,
// multipleSelect,
// number,
// o2m,
// o2o,
// password,
// percent,
// phone,
// radioGroup,
// richText,
// select,
// subTable,
// tableoid,
// textarea,
// time,
// updatedAt,
// updatedBy,
// url,
// ]);
this.app.collectionManager.addFieldInterfaces([
checkbox,
checkboxGroup,
chinaRegion,
collection,
color,
createdAt,
createdBy,
datetime,
email,
icon,
id,
input,
integer,
json,
linkTo,
m2m,
m2o,
markdown,
multipleSelect,
number,
o2m,
o2o,
password,
percent,
phone,
radioGroup,
richText,
select,
subTable,
tableoid,
textarea,
time,
updatedAt,
updatedBy,
url,
]);
}
addCollectionTemplates() {
this.app.collectionManager.addCollectionTemplates(Object.values(collectionTemplates));
// this.app.collectionManager.addCollectionTemplates([expression, general, sql, tree, view]);
this.app.collectionManager.addCollectionTemplates([expression, general, sql, tree, view]);
}
async reloadCollections() {
@ -115,6 +140,6 @@ export class CollectionPlugin extends Plugin {
},
});
return [...(service?.data?.data || []), collection];
return [...(service?.data?.data || []), collectionData];
}
}

View File

@ -11,7 +11,7 @@ export * from './CollectionManagerSchemaComponentProvider';
export * from './CollectionManagerShortcut';
export * from './CollectionProvider';
export * from './Configuration';
export { registerField, registerGroup, registerGroupLabel, useFieldInterfaceOptions } from './Configuration/interfaces';
export { useFieldInterfaceOptions } from './Configuration/interfaces';
export * from './context';
export * from './hooks';
export * as interfacesProperties from './interfaces/properties';

View File

@ -463,7 +463,6 @@ AuditLogs.Decorator = observer(
(props: any) => {
const parent = useCollection();
const record = useRecord();
const { interfaces } = useCollectionManager();
let filter = props?.params?.filter;
if (parent.name) {
const filterByTk = record?.[parent.filterTargetKey || 'id'];
@ -501,7 +500,7 @@ AuditLogs.Decorator = observer(
};
return (
<IsAssociationBlock.Provider value={!!parent.name}>
<CollectionManagerProvider collections={[collection]} interfaces={interfaces}>
<CollectionManagerProvider collections={[collection]}>
<TableBlockProvider {...defaults}>{props.children}</TableBlockProvider>
</CollectionManagerProvider>
</IsAssociationBlock.Provider>

View File

@ -4,18 +4,15 @@ import { MapBlockOptions } from './block';
import { mapActionInitializers } from './block/MapActionInitializers';
import { Configuration, Map } from './components';
import { fields } from './fields';
import { MapInitializer } from './initialize';
import { generateNTemplate } from './locale';
import { NAMESPACE } from './locale';
const MapProvider = React.memo((props) => {
return (
<CurrentAppInfoProvider>
<MapInitializer>
<SchemaComponentOptions components={{ Map }}>
<MapBlockOptions>{props.children}</MapBlockOptions>
</SchemaComponentOptions>
</MapInitializer>
<SchemaComponentOptions components={{ Map }}>
<MapBlockOptions>{props.children}</MapBlockOptions>
</SchemaComponentOptions>
</CurrentAppInfoProvider>
);
});
@ -26,6 +23,12 @@ export class MapPlugin extends Plugin {
this.app.use(MapProvider);
this.app.collectionManager.addFieldInterfaces(fields);
this.app.collectionManager.addFieldGroups({
map: {
label: generateNTemplate('Map-based geometry'),
order: 51,
},
});
this.app.schemaInitializerManager.add(mapActionInitializers);
const blockInitializers = this.app.schemaInitializerManager.get('BlockInitializers');

View File

@ -1,32 +0,0 @@
import { registerField, registerGroup, useCurrentAppInfo } from '@nocobase/client';
import React, { useEffect } from 'react';
import { fields } from './fields';
import { generateNTemplate } from './locale';
import './locale';
export const useRegisterInterface = () => {
const { data } = useCurrentAppInfo() || {};
useEffect(() => {
const dialect = data?.database.dialect;
if (!dialect) return;
registerGroup(fields[0].group, {
label: generateNTemplate('Map-based geometry'),
order: 51,
});
fields.forEach((field) => {
if (Array.isArray(field.dialects)) {
if (!field.dialects.includes(dialect)) {
return;
}
}
registerField(field.group, field.name as string, field);
});
}, [data]);
};
export const MapInitializer: React.FC = (props) => {
useRegisterInterface();
return <React.Fragment>{props.children}</React.Fragment>;
};