mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-12-02 12:18:15 +08:00
feat: snapshot field plugin (#1253)
* feat: snapshort init * feat: snapshot update yarn.lock * feat: snapshot add to preset * feat: snapshot add field fix * feat: snapshot remove Table Column SnapshotField * feat: snapshot field label fix * feat: snapshot request error fix * feat: snapshot 二级关联数据打开 * feat: snapshot batch edit fix * feat: snapshot 2 level draw fix * feat: snapshot translate * feat: snapshot global historyCollection provider * feat: snapshot install initial * feat: snapshot refreshCH * feat: snapshot add transaction * feat: snapshot default collecitonField * feat: snapshot build fix * feat: snapshot useSnapshotFieldTargetCollectionKey * feat: snapshot batch update * feat: snapshot linkto support * feat: snapshot use getRepository * feat: snapshot recreate fix * feat: snapshot collectionKey to collectionName & rebuild collection * feat: snapshot remove SnapshotHistoryCollectionProvider & collectionName * feat: snapshot use historyCollections in inherit table * feat: snapshot fix TableSelectorBlock appends * feat: snapshot kanban fix * feat: snapshot snapshot association field fix * feat: snapshot add CollectionFieldProvider fallback * feat: snapshot AssociationSelect fix * feat: snapshot TableField fix
This commit is contained in:
parent
a44b778098
commit
6febdb041a
1
packages/app/client/src/plugins/snapshot-field.ts
Normal file
1
packages/app/client/src/plugins/snapshot-field.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from '@nocobase/plugin-snapshot-field/client';
|
@ -1,3 +1,4 @@
|
||||
export * from './hooks';
|
||||
export * from './APIClient';
|
||||
export * from './APIClientProvider';
|
||||
export * from './context';
|
||||
|
@ -8,19 +8,22 @@ import React, { createContext, useContext } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
ACLCollectionProvider,
|
||||
TableFieldContext,
|
||||
TableFieldResource,
|
||||
useActionContext,
|
||||
useAPIClient,
|
||||
useDesignable,
|
||||
useRecord,
|
||||
useTableFieldContext,
|
||||
WithoutTableFieldResource,
|
||||
} from '../';
|
||||
import { CollectionProvider, useCollection, useCollectionManager } from '../collection-manager';
|
||||
import { CollectionProvider, useCollection, useCollectionField, useCollectionManager } from '../collection-manager';
|
||||
import { useRecordIndex } from '../record-provider';
|
||||
import { SharedFilterProvider } from './SharedFilterProvider';
|
||||
|
||||
export const BlockResourceContext = createContext(null);
|
||||
export const BlockAssociationContext = createContext(null);
|
||||
const BlockRequestContext = createContext<any>(null);
|
||||
export const BlockRequestContext = createContext<any>(null);
|
||||
|
||||
export const useBlockResource = () => {
|
||||
return useContext(BlockResourceContext);
|
||||
@ -86,26 +89,36 @@ const useActionParams = (props) => {
|
||||
};
|
||||
|
||||
export const useResourceAction = (props, opts = {}) => {
|
||||
const { resource, action } = props;
|
||||
/**
|
||||
* fieldName: 来自 TableFieldProvider
|
||||
*/
|
||||
const { resource, action, fieldName: tableFieldName } = props;
|
||||
const { fields } = useCollection();
|
||||
const appends = fields?.filter((field) => field.target).map((field) => field.name);
|
||||
const appends = fields?.filter((field) => field.target && field.interface !== 'snapshot').map((field) => field.name);
|
||||
const params = useActionParams(props);
|
||||
const api = useAPIClient();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { snapshot } = useActionContext();
|
||||
const record = useRecord();
|
||||
|
||||
if (!Object.keys(params).includes('appends') && appends?.length) {
|
||||
params['appends'] = appends;
|
||||
}
|
||||
const result = useRequest(
|
||||
(opts) => {
|
||||
if (!action) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
const actionParams = { ...opts };
|
||||
if (params.appends) {
|
||||
actionParams.appends = params.appends;
|
||||
}
|
||||
return resource[action](actionParams).then((res) => res.data);
|
||||
},
|
||||
snapshot
|
||||
? async () => ({
|
||||
data: record[tableFieldName] ?? [],
|
||||
})
|
||||
: (opts) => {
|
||||
if (!action) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
const actionParams = { ...opts };
|
||||
if (params.appends) {
|
||||
actionParams.appends = params.appends;
|
||||
}
|
||||
return resource[action](actionParams).then((res) => res.data);
|
||||
},
|
||||
{
|
||||
...opts,
|
||||
onSuccess(data, params) {
|
||||
@ -121,7 +134,7 @@ export const useResourceAction = (props, opts = {}) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const MaybeCollectionProvider = (props) => {
|
||||
export const MaybeCollectionProvider = (props) => {
|
||||
const { collection } = props;
|
||||
return collection ? (
|
||||
<CollectionProvider collection={collection}>
|
||||
|
@ -75,7 +75,7 @@ const useAssociationNames = (collection) => {
|
||||
const collectionFields = getCollectionFields(collection);
|
||||
const associationFields = new Set();
|
||||
for (const collectionField of collectionFields) {
|
||||
if (collectionField.target) {
|
||||
if (collectionField.target && collectionField.interface !== 'snapshot') {
|
||||
associationFields.add(collectionField.name);
|
||||
const fields = getCollectionFields(collectionField.target);
|
||||
for (const field of fields) {
|
||||
@ -104,7 +104,6 @@ const useAssociationNames = (collection) => {
|
||||
export const KanbanBlockProvider = (props) => {
|
||||
const params = { ...props.params };
|
||||
const appends = useAssociationNames(props.collection);
|
||||
console.log('KanbanBlockProvider', appends);
|
||||
if (!Object.keys(params).includes('appends')) {
|
||||
params['appends'] = appends;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ export const useAssociationNames = (collection) => {
|
||||
const collectionFields = getCollectionFields(collection);
|
||||
const associationFields = new Set();
|
||||
for (const collectionField of collectionFields) {
|
||||
if (collectionField.target) {
|
||||
if (collectionField.target && collectionField.interface !== 'snapshot') {
|
||||
associationFields.add(collectionField.name);
|
||||
const fields = getCollectionFields(collectionField.target);
|
||||
for (const field of fields) {
|
||||
|
@ -2,6 +2,7 @@ import { ArrayField, Field } from '@formily/core';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import React, { createContext, useContext, useEffect } from 'react';
|
||||
import { APIClient } from '../api-client';
|
||||
import { useCollectionField } from '../collection-manager';
|
||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||
import { useFormBlockContext } from './FormBlockProvider';
|
||||
import { useFormFieldContext } from './FormFieldProvider';
|
||||
|
@ -37,7 +37,7 @@ const InternalTableSelectorProvider = (props) => {
|
||||
const useAssociationNames2 = (collection) => {
|
||||
const { getCollectionFields } = useCollectionManager();
|
||||
const names = getCollectionFields(collection)
|
||||
?.filter((field) => field.target)
|
||||
?.filter((field) => field.target && field.interface !== 'snapshot')
|
||||
.map((field) => field.name);
|
||||
return names;
|
||||
};
|
||||
@ -55,7 +55,7 @@ const useAssociationNames = (collection) => {
|
||||
const collectionFields = getCollectionFields(collection);
|
||||
const associationFields = new Set();
|
||||
for (const collectionField of collectionFields) {
|
||||
if (collectionField.target) {
|
||||
if (collectionField.target && collectionField.interface !== 'snapshot') {
|
||||
associationFields.add(collectionField.name);
|
||||
const fields = getCollectionFields(collectionField.target);
|
||||
for (const field of fields) {
|
||||
|
@ -3,7 +3,7 @@ import { connect, useField, useFieldSchema } from '@formily/react';
|
||||
import { merge } from '@formily/shared';
|
||||
import { concat } from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useCompile, useComponent, useFormBlockContext } from '..';
|
||||
import { useActionContext, useCompile, useComponent, useFormBlockContext, useRecord } from '..';
|
||||
import { CollectionFieldProvider } from './CollectionFieldProvider';
|
||||
import { useCollectionField } from './hooks';
|
||||
|
||||
@ -77,11 +77,43 @@ const InternalField: React.FC = (props) => {
|
||||
return React.createElement(component, props, props.children);
|
||||
};
|
||||
|
||||
export const InternalFallbackField = () => {
|
||||
const { uiSchema } = useCollectionField();
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const record = useRecord();
|
||||
|
||||
const displayKey = fieldSchema['x-component-props']?.fieldNames?.label ?? 'id';
|
||||
|
||||
const value = record[fieldSchema.name];
|
||||
|
||||
useEffect(() => {
|
||||
field.title = fieldSchema.title ?? fieldSchema.name;
|
||||
}, [uiSchema?.title]);
|
||||
|
||||
let displayText = value;
|
||||
|
||||
if (Array.isArray(value) || typeof value === 'object') {
|
||||
displayText = []
|
||||
.concat(value)
|
||||
.map((i) => i[displayKey])
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
return <div>{displayText}</div>;
|
||||
};
|
||||
|
||||
export const CollectionField = connect((props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = fieldSchema?.['x-component-props']?.['field'];
|
||||
const { snapshot } = useActionContext();
|
||||
|
||||
return (
|
||||
<CollectionFieldProvider name={fieldSchema.name} field={field}>
|
||||
<CollectionFieldProvider
|
||||
name={fieldSchema.name}
|
||||
field={field}
|
||||
fallback={snapshot ? <InternalFallbackField /> : null}
|
||||
>
|
||||
<InternalField {...props} />
|
||||
</CollectionFieldProvider>
|
||||
);
|
||||
|
@ -4,18 +4,18 @@ import { CollectionFieldContext } from './context';
|
||||
import { useCollection, useCollectionManager } from './hooks';
|
||||
import { CollectionFieldOptions } from './types';
|
||||
|
||||
export const CollectionFieldProvider: React.FC<{ name?: SchemaKey; field?: CollectionFieldOptions }> = (props) => {
|
||||
const { name, field, children } = props;
|
||||
export const CollectionFieldProvider: React.FC<{
|
||||
name?: SchemaKey;
|
||||
field?: CollectionFieldOptions;
|
||||
fallback?: React.ReactElement;
|
||||
}> = (props) => {
|
||||
const { name, field, children, fallback } = props;
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const { getCollectionJoinField } = useCollectionManager();
|
||||
const value = field || getField(field?.name || name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
|
||||
if (!value) {
|
||||
return null;
|
||||
return fallback;
|
||||
}
|
||||
return (
|
||||
<CollectionFieldContext.Provider value={value}>
|
||||
{children}
|
||||
</CollectionFieldContext.Provider>
|
||||
);
|
||||
return <CollectionFieldContext.Provider value={value}>{children}</CollectionFieldContext.Provider>;
|
||||
};
|
||||
|
@ -0,0 +1,63 @@
|
||||
import { Spin } from 'antd';
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { APIClientContext, useRequest } from '../api-client';
|
||||
|
||||
export interface CollectionHistoryContextValue {
|
||||
historyCollections: any[];
|
||||
refreshCH: () => Promise<any>;
|
||||
}
|
||||
|
||||
const CollectionHistoryContext = createContext<CollectionHistoryContextValue>({
|
||||
historyCollections: [],
|
||||
refreshCH: () => undefined,
|
||||
});
|
||||
|
||||
export const CollectionHistoryProvider: React.FC = (props) => {
|
||||
const api = useContext(APIClientContext);
|
||||
|
||||
const options = {
|
||||
resource: 'collectionsHistory',
|
||||
action: 'list',
|
||||
params: {
|
||||
paginate: false,
|
||||
appends: ['fields', 'fields.uiSchema'],
|
||||
filter: {
|
||||
// inherit: false,
|
||||
},
|
||||
sort: ['sort'],
|
||||
},
|
||||
};
|
||||
|
||||
const service = useRequest(options);
|
||||
|
||||
// 刷新 collecionHistory
|
||||
const refreshCH = async () => {
|
||||
const { data } = await api.request(options);
|
||||
service.mutate(data);
|
||||
return data?.data || [];
|
||||
};
|
||||
|
||||
if (service.loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
return (
|
||||
<CollectionHistoryContext.Provider
|
||||
value={{
|
||||
historyCollections: service.data?.data,
|
||||
refreshCH,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</CollectionHistoryContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useHistoryCollectionsByNames = (collectionNames: string[]) => {
|
||||
const { historyCollections } = useContext(CollectionHistoryContext);
|
||||
return historyCollections.filter((i) => collectionNames.includes(i.name));
|
||||
};
|
||||
|
||||
export const useCollectionHistory = () => {
|
||||
return useContext(CollectionHistoryContext);
|
||||
};
|
@ -4,9 +4,10 @@ import { keyBy } from 'lodash';
|
||||
import { useAPIClient, useRequest } from '../api-client';
|
||||
import { CollectionManagerSchemaComponentProvider } from './CollectionManagerSchemaComponentProvider';
|
||||
import { CollectionManagerContext } from './context';
|
||||
import * as defaultInterfaces from './interfaces';
|
||||
import { CollectionManagerOptions } from './types';
|
||||
import { templateOptions } from '../collection-manager/Configuration/templates';
|
||||
import * as defaultInterfaces from './interfaces';
|
||||
import { useCollectionHistory } from './CollectionHistoryProvider';
|
||||
|
||||
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
|
||||
const { service, interfaces, collections = [], refreshCM, templates } = props;
|
||||
@ -31,6 +32,7 @@ export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (pr
|
||||
export const RemoteCollectionManagerProvider = (props: any) => {
|
||||
const api = useAPIClient();
|
||||
const [contentLoading, setContentLoading] = useState(false);
|
||||
const { refreshCH } = useCollectionHistory();
|
||||
const options = {
|
||||
resource: 'collections',
|
||||
action: 'list',
|
||||
@ -47,21 +49,25 @@ export const RemoteCollectionManagerProvider = (props: any) => {
|
||||
if (service.loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
const refreshCM = async (opts) => {
|
||||
if (opts?.reload) {
|
||||
setContentLoading(true);
|
||||
}
|
||||
const { data } = await api.request(options);
|
||||
service.mutate(data);
|
||||
await refreshCH();
|
||||
if (opts?.reload) {
|
||||
setContentLoading(false);
|
||||
}
|
||||
return data?.data || [];
|
||||
};
|
||||
|
||||
return (
|
||||
<CollectionManagerProvider
|
||||
service={{ ...service, contentLoading, setContentLoading }}
|
||||
collections={service?.data?.data}
|
||||
refreshCM={async (opts) => {
|
||||
if (opts?.reload) {
|
||||
setContentLoading(true);
|
||||
}
|
||||
const { data } = await api.request(options);
|
||||
service.mutate(data);
|
||||
if (opts?.reload) {
|
||||
setContentLoading(false);
|
||||
}
|
||||
return data?.data || [];
|
||||
}}
|
||||
refreshCM={refreshCM}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
@ -11,6 +11,7 @@ import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useCancelAction } from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import { useOptions } from '../hooks/useOptions';
|
||||
import { IField } from '../interfaces/types';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import * as components from './components';
|
||||
@ -148,6 +149,7 @@ export const AddFieldAction = (props) => {
|
||||
const [schema, setSchema] = useState({});
|
||||
const compile = useCompile();
|
||||
const { t } = useTranslation();
|
||||
const options = useOptions();
|
||||
const getFieldOptions = () => {
|
||||
const { availableFieldInterfaces } = getTemplate(record.template) || {};
|
||||
const { exclude, include } = availableFieldInterfaces || {};
|
||||
|
@ -10,9 +10,9 @@ import { useRequest } from '../../api-client';
|
||||
import { RecordProvider } from '../../record-provider';
|
||||
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import { useOptions } from '../hooks/useOptions';
|
||||
import { IField } from '../interfaces/types';
|
||||
import * as components from './components';
|
||||
import { options } from './interfaces';
|
||||
|
||||
const getSchema = (schema: IField): ISchema => {
|
||||
if (!schema) {
|
||||
@ -99,6 +99,7 @@ export const AddSubFieldAction = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [schema, setSchema] = useState({});
|
||||
const compile = useCompile();
|
||||
const options = useOptions();
|
||||
const { t } = useTranslation();
|
||||
const items = options.map((option) => {
|
||||
const children = option.children.map((child) => {
|
||||
|
@ -72,7 +72,7 @@ const useNewId = (prefix) => {
|
||||
};
|
||||
|
||||
export const ConfigurationTable = () => {
|
||||
const { collections = [] } = useCollectionManager();
|
||||
const { collections = [], interfaces } = useCollectionManager();
|
||||
const {
|
||||
data: { database },
|
||||
} = useCurrentAppInfo();
|
||||
@ -114,6 +114,7 @@ export const ConfigurationTable = () => {
|
||||
useCurrentFields,
|
||||
useNewId,
|
||||
useCancelAction,
|
||||
interfaces,
|
||||
enableInherits: database?.dialect === 'postgres',
|
||||
}}
|
||||
/>
|
||||
|
@ -3,7 +3,6 @@ import { registerValidateFormats } from '@formily/core';
|
||||
export * from './AddFieldAction';
|
||||
export * from './ConfigurationTable';
|
||||
export * from './EditFieldAction';
|
||||
export * from './interfaces';
|
||||
export * from './components';
|
||||
export * from './templates';
|
||||
export * from './CollectionFieldsTable';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { CollectionOptions } from '../../types';
|
||||
import { CollectionFieldInterface } from '../components/CollectionFieldInterface';
|
||||
import { options } from '../interfaces';
|
||||
|
||||
const collection: CollectionOptions = {
|
||||
name: 'fields',
|
||||
@ -31,7 +30,7 @@ const collection: CollectionOptions = {
|
||||
title: '{{ t("Field interface") }}',
|
||||
type: 'string',
|
||||
'x-component': 'Select',
|
||||
enum: options as any,
|
||||
enum: '{{interfaces}}',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -0,0 +1,45 @@
|
||||
import set from 'lodash/set';
|
||||
import { useCollectionManager } from './useCollectionManager';
|
||||
|
||||
export const useOptions = () => {
|
||||
const { interfaces } = useCollectionManager();
|
||||
|
||||
const fields = {};
|
||||
|
||||
Object.keys(interfaces).forEach((type) => {
|
||||
const schema = interfaces[type];
|
||||
registerField(schema.group || 'others', type, { order: 0, ...schema });
|
||||
});
|
||||
|
||||
function registerField(group: string, type: string, schema) {
|
||||
fields[group] = fields[group] || {};
|
||||
set(fields, [group, type], schema);
|
||||
}
|
||||
|
||||
const groupLabels = {
|
||||
basic: '{{t("Basic")}}',
|
||||
choices: '{{t("Choices")}}',
|
||||
media: '{{t("Media")}}',
|
||||
datetime: '{{t("Date & Time")}}',
|
||||
relation: '{{t("Relation")}}',
|
||||
advanced: '{{t("Advanced type")}}',
|
||||
systemInfo: '{{t("System info")}}',
|
||||
others: '{{t("Others")}}',
|
||||
};
|
||||
|
||||
return Object.keys(groupLabels).map((groupName) => ({
|
||||
label: groupLabels[groupName],
|
||||
key: groupName,
|
||||
children: Object.keys(fields[groupName] || {})
|
||||
.map((type) => {
|
||||
const field = fields[groupName][type];
|
||||
return {
|
||||
value: type,
|
||||
label: field.title,
|
||||
name: type,
|
||||
...fields[groupName][type],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.order - b.order),
|
||||
}));
|
||||
};
|
@ -15,3 +15,4 @@ export * from './ResourceActionProvider';
|
||||
export { getConfigurableProperties } from './templates/properties';
|
||||
export * from './templates/types';
|
||||
export * from './types';
|
||||
export * from './CollectionHistoryProvider';
|
||||
|
@ -29,6 +29,7 @@ export const type: ISchema = {
|
||||
{ label: 'One to many', value: 'hasMany' },
|
||||
{ label: 'Many to one', value: 'belongsTo' },
|
||||
{ label: 'Many to many', value: 'belongsToMany' },
|
||||
{ label: 'Snapshot', value: 'snapshot' },
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -8,10 +8,9 @@ import {
|
||||
useCollectionManager,
|
||||
useRecord,
|
||||
useRecordIndex,
|
||||
useRequest
|
||||
useRequest,
|
||||
} from '../';
|
||||
import { useAPIClient } from '../api-client';
|
||||
import { options } from './Configuration/interfaces';
|
||||
|
||||
const collection: CollectionOptions = {
|
||||
name: 'fields',
|
||||
@ -42,7 +41,7 @@ const collection: CollectionOptions = {
|
||||
title: '{{ t("Field interface") }}',
|
||||
type: 'string',
|
||||
'x-component': 'Select',
|
||||
enum: options as any,
|
||||
enum: '{{interfaces}}',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ export interface ActionContextProps {
|
||||
visible?: boolean;
|
||||
setVisible?: (v: boolean) => void;
|
||||
openMode?: 'drawer' | 'modal' | 'page';
|
||||
snapshot?: boolean;
|
||||
openSize?: OpenSize;
|
||||
containerRefKey?: string;
|
||||
formValueChanged?: boolean;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useFieldSchema } from "@formily/react";
|
||||
import { useMemo } from "react";
|
||||
import { useCollection } from "../../../collection-manager";
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useMemo } from 'react';
|
||||
import { useCollection } from '../../../collection-manager';
|
||||
|
||||
export default function useServiceOptions(props) {
|
||||
const { action = 'list', service } = props
|
||||
const { action = 'list', service } = props;
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const collectionField = useMemo(() => {
|
||||
@ -12,9 +12,9 @@ export default function useServiceOptions(props) {
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
resource: collectionField.target,
|
||||
resource: collectionField?.target,
|
||||
action,
|
||||
...service,
|
||||
};
|
||||
}, [collectionField.target, action, service]);
|
||||
}, [collectionField?.target, action, service]);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from './Input';
|
||||
export * from './ReadPretty';
|
||||
export * from './Json';
|
||||
export * from './EllipsisWithTooltip';
|
||||
|
@ -7,7 +7,7 @@ import { CollectionProvider, useCollection, useCollectionManager } from '../../.
|
||||
import { RecordProvider, useRecord } from '../../../record-provider';
|
||||
import { FormProvider } from '../../core';
|
||||
import { useCompile } from '../../hooks';
|
||||
import { ActionContext } from '../action';
|
||||
import { ActionContext, useActionContext } from '../action';
|
||||
import { EllipsisWithTooltip } from '../input/EllipsisWithTooltip';
|
||||
import { useFieldNames } from './useFieldNames';
|
||||
import { getLabelFormatValue, useLabelUiSchema } from './util';
|
||||
@ -28,7 +28,8 @@ export const ReadPrettyRecordPicker: React.FC = observer((props: any) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const recordCtx = useRecord();
|
||||
const { getCollectionJoinField } = useCollectionManager();
|
||||
const field = useField<Field>();
|
||||
// value 做了转换,但 props.value 和原来 useField().value 的值不一致
|
||||
// const field = useField<Field>();
|
||||
const fieldNames = useFieldNames(props);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [popoverVisible, setPopoverVisible] = useState<boolean>();
|
||||
@ -37,16 +38,18 @@ export const ReadPrettyRecordPicker: React.FC = observer((props: any) => {
|
||||
const [record, setRecord] = useState({});
|
||||
const compile = useCompile();
|
||||
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
|
||||
const { snapshot } = useActionContext();
|
||||
|
||||
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
|
||||
const renderRecords = () =>
|
||||
toArr(field.value).map((record, index, arr) => {
|
||||
toArr(props.value).map((record, index, arr) => {
|
||||
const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A');
|
||||
return (
|
||||
<Fragment key={`${record.id}_${index}`}>
|
||||
<span>
|
||||
<a
|
||||
onClick={(e) => {
|
||||
if (snapshot) return;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setVisible(true);
|
||||
@ -95,7 +98,9 @@ export const ReadPrettyRecordPicker: React.FC = observer((props: any) => {
|
||||
<EllipsisWithTooltip ellipsis={ellipsis} ref={ellipsisWithTooltipRef}>
|
||||
{renderRecords()}
|
||||
</EllipsisWithTooltip>
|
||||
<ActionContext.Provider value={{ visible, setVisible, openMode: 'drawer' }}>
|
||||
<ActionContext.Provider
|
||||
value={{ visible, setVisible, openMode: 'drawer', snapshot: collectionField.interface === 'snapshot' }}
|
||||
>
|
||||
{renderRecordProvider()}
|
||||
</ActionContext.Provider>
|
||||
</CollectionProvider>
|
||||
|
@ -5,4 +5,7 @@ RecordPicker.Viewer = Action.Container;
|
||||
RecordPicker.Selector = Action.Container;
|
||||
|
||||
export { RecordPicker };
|
||||
|
||||
export * from './useFieldNames';
|
||||
export * from './util';
|
||||
export * from './ReadPrettyRecordPicker';
|
||||
export * from './InputRecordPicker';
|
||||
|
@ -1,26 +1,35 @@
|
||||
import { observer, useField } from '@formily/react';
|
||||
import { observer, useField, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { getValues } from './shared';
|
||||
import { Select, defaultFieldNames } from '../select';
|
||||
import { useRequest } from '../../../api-client';
|
||||
import { useRecord } from '../../../record-provider';
|
||||
import { useActionContext } from '../action';
|
||||
|
||||
export const ReadPretty = observer((props: any) => {
|
||||
const fieldNames = { ...defaultFieldNames, ...props.fieldNames };
|
||||
const field = useField<any>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const record = useRecord();
|
||||
const { snapshot } = useActionContext();
|
||||
|
||||
const { data } = useRequest(
|
||||
{
|
||||
action: 'list',
|
||||
...props.service,
|
||||
params: {
|
||||
paginate: false,
|
||||
filter: {
|
||||
[fieldNames.value]: {
|
||||
$in: getValues(field.value, fieldNames),
|
||||
snapshot
|
||||
? async () => ({
|
||||
data: record[fieldSchema.name],
|
||||
})
|
||||
: {
|
||||
action: 'list',
|
||||
...props.service,
|
||||
params: {
|
||||
paginate: false,
|
||||
filter: {
|
||||
[fieldNames.value]: {
|
||||
$in: getValues(field.value, fieldNames),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
refreshDeps: [props.service, field.value],
|
||||
},
|
||||
|
@ -2,6 +2,7 @@ import { useField, useFieldSchema } from '@formily/react';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { SortableItem, useCollection, useCollectionManager, useCompile, useDesignable, useDesigner } from '../../../';
|
||||
import { designerCss } from './Table.Column.ActionBar';
|
||||
import { isCollectionFieldComponent } from './utils';
|
||||
|
||||
export const useColumnSchema = () => {
|
||||
const { getField } = useCollection();
|
||||
@ -9,7 +10,7 @@ export const useColumnSchema = () => {
|
||||
const columnSchema = useFieldSchema();
|
||||
const { getCollectionJoinField } = useCollectionManager();
|
||||
const fieldSchema = columnSchema.reduceProperties((buf, s) => {
|
||||
if (s['x-component'] === 'CollectionField') {
|
||||
if (isCollectionFieldComponent(s)) {
|
||||
return s;
|
||||
}
|
||||
return buf;
|
||||
@ -17,7 +18,7 @@ export const useColumnSchema = () => {
|
||||
if (!fieldSchema) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
|
||||
return { columnSchema, fieldSchema, collectionField, uiSchema: compile(collectionField?.uiSchema) };
|
||||
};
|
||||
@ -41,7 +42,7 @@ export const TableColumnDecorator = (props) => {
|
||||
}, [uiSchema?.title]);
|
||||
return (
|
||||
<SortableItem className={designerCss}>
|
||||
<Designer fieldSchema={fieldSchema} uiSchema={uiSchema} collectionField={collectionField}/>
|
||||
<Designer fieldSchema={fieldSchema} uiSchema={uiSchema} collectionField={collectionField} />
|
||||
{/* <RecursionField name={columnSchema.name} schema={columnSchema}/> */}
|
||||
{field?.title || compile(uiSchema?.title)}
|
||||
{/* <div
|
||||
|
@ -120,7 +120,7 @@ export const TableColumnDesigner = (props) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho'].includes(collectionField?.interface) && (
|
||||
{['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho', 'snapshot'].includes(collectionField?.interface) && (
|
||||
<SchemaSettings.SelectItem
|
||||
title={t('Title field')}
|
||||
options={options}
|
||||
|
@ -11,14 +11,7 @@ import React, { RefCallback, useCallback, useEffect, useMemo, useRef, useState }
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DndContext, useDesignable } from '../..';
|
||||
import { RecordIndexProvider, RecordProvider, useSchemaInitializer } from '../../../';
|
||||
|
||||
const isColumnComponent = (schema: Schema) => {
|
||||
return schema['x-component']?.endsWith('.Column') > -1;
|
||||
};
|
||||
|
||||
const isCollectionFieldComponent = (schema: ISchema) => {
|
||||
return schema['x-component'] === 'CollectionField';
|
||||
};
|
||||
import { isCollectionFieldComponent, isColumnComponent } from './utils';
|
||||
|
||||
const useTableColumns = () => {
|
||||
const start = Date.now();
|
||||
@ -48,7 +41,6 @@ const useTableColumns = () => {
|
||||
...s['x-component-props'],
|
||||
render: (v, record) => {
|
||||
const index = field.value?.indexOf(record);
|
||||
// console.log((Date.now() - start) / 1000);
|
||||
return (
|
||||
<RecordIndexProvider index={index}>
|
||||
<RecordProvider record={record}>
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { ISchema, Schema } from '@formily/react';
|
||||
|
||||
export const isCollectionFieldComponent = (schema: ISchema) => {
|
||||
return schema['x-component'] === 'CollectionField';
|
||||
};
|
||||
|
||||
export const isColumnComponent = (schema: Schema) => {
|
||||
return schema['x-component']?.endsWith('.Column') > -1;
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
export * from './SchemaInitializer';
|
||||
export * from './SchemaInitializerProvider';
|
||||
export * from './types';
|
||||
|
||||
export * from './items';
|
||||
export { gridRowColWrap, useRecordCollectionDataSourceItems } from './utils';
|
||||
|
@ -4,7 +4,7 @@ import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaInitializerItemOptions } from '../';
|
||||
import { useCollection, useCollectionManager } from '../collection-manager';
|
||||
import { useDesignable } from '../schema-component';
|
||||
import { useActionContext, useDesignable } from '../schema-component';
|
||||
import { useSchemaTemplateManager } from '../schema-templates';
|
||||
import { SelectCollection } from './SelectCollection';
|
||||
|
||||
@ -193,6 +193,9 @@ export const useFormItemInitializerFields = (options?: any) => {
|
||||
const { getInterface } = useCollectionManager();
|
||||
const form = useForm();
|
||||
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||
const actionCtx = useActionContext();
|
||||
const action = actionCtx?.fieldSchema?.['x-action'];
|
||||
const { snapshot } = useActionContext();
|
||||
|
||||
return currentFields
|
||||
?.filter((field) => field?.interface && !field?.isForeignKey)
|
||||
@ -204,7 +207,7 @@ export const useFormItemInitializerFields = (options?: any) => {
|
||||
name: field.name,
|
||||
// title: field?.uiSchema?.title || field.name,
|
||||
'x-designer': 'FormItem.Designer',
|
||||
'x-component': field.interface === 'o2m' ? 'TableField' : 'CollectionField',
|
||||
'x-component': field.interface === 'o2m' && !snapshot ? 'TableField' : 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': `${name}.${field.name}`,
|
||||
'x-component-props': {},
|
||||
@ -217,7 +220,7 @@ export const useFormItemInitializerFields = (options?: any) => {
|
||||
component: 'CollectionFieldInitializer',
|
||||
remove: removeGridFormItem,
|
||||
schemaInitialize: (s) => {
|
||||
interfaceConfig?.schemaInitialize?.(s, { field, block, readPretty });
|
||||
interfaceConfig?.schemaInitialize?.(s, { field, block, readPretty, action });
|
||||
},
|
||||
schema,
|
||||
} as SchemaInitializerItemOptions;
|
||||
@ -288,6 +291,8 @@ export const useInheritsFormItemInitializerFields = (options?) => {
|
||||
const { name } = useCollection();
|
||||
const { getInterface, getInheritCollections, getCollection, getParentCollectionFields } = useCollectionManager();
|
||||
const inherits = getInheritCollections(name);
|
||||
const { snapshot } = useActionContext();
|
||||
|
||||
return inherits?.map((v) => {
|
||||
const fields = getParentCollectionFields(v, name);
|
||||
const form = useForm();
|
||||
@ -303,7 +308,7 @@ export const useInheritsFormItemInitializerFields = (options?) => {
|
||||
name: field.name,
|
||||
title: field?.uiSchema?.title || field.name,
|
||||
'x-designer': 'FormItem.Designer',
|
||||
'x-component': field.interface === 'o2m' ? 'TableField' : 'CollectionField',
|
||||
'x-component': field.interface === 'o2m' && !snapshot ? 'TableField' : 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-collection-field': `${name}.${field.name}`,
|
||||
'x-component-props': {},
|
||||
@ -331,7 +336,7 @@ export const useCustomFormItemInitializerFields = (options?: any) => {
|
||||
const remove = useRemoveGridFormItem();
|
||||
return currentFields
|
||||
?.filter((field) => {
|
||||
return field?.interface && !field?.uiSchema?.['x-read-pretty'];
|
||||
return field?.interface && !field?.uiSchema?.['x-read-pretty'] && field.interface !== 'snapshot';
|
||||
})
|
||||
?.map((field) => {
|
||||
const interfaceConfig = getInterface(field.interface);
|
||||
@ -365,7 +370,7 @@ export const useCustomBulkEditFormItemInitializerFields = (options?: any) => {
|
||||
const remove = useRemoveGridFormItem();
|
||||
return fields
|
||||
?.filter((field) => {
|
||||
return field?.interface && !field?.uiSchema?.['x-read-pretty'];
|
||||
return field?.interface && !field?.uiSchema?.['x-read-pretty'] && field.interface !== 'snapshot';
|
||||
})
|
||||
?.map((field) => {
|
||||
const interfaceConfig = getInterface(field.interface);
|
||||
|
@ -113,8 +113,8 @@ export class Auth {
|
||||
url: 'users:signin',
|
||||
data: values,
|
||||
params: {
|
||||
authenticator
|
||||
}
|
||||
authenticator,
|
||||
},
|
||||
});
|
||||
const data = response?.data?.data;
|
||||
this.setToken(data?.token);
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from './repositories';
|
||||
export { default as fieldsCollection } from './collections/fields';
|
||||
export { default as collectionsCollection } from './collections/collections';
|
||||
export { default } from './server';
|
||||
|
||||
|
4
packages/plugins/snapshot-field/client.d.ts
vendored
Executable file
4
packages/plugins/snapshot-field/client.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/client';
|
||||
export { default } from './lib/client';
|
||||
|
30
packages/plugins/snapshot-field/client.js
Executable file
30
packages/plugins/snapshot-field/client.js
Executable file
@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
|
||||
var _index = _interopRequireWildcard(require("./lib/client"));
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
var _exportNames = {};
|
||||
Object.defineProperty(exports, "default", {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index.default;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(_index).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||
if (key in exports && exports[key] === _index[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index[key];
|
||||
}
|
||||
});
|
||||
});
|
9
packages/plugins/snapshot-field/package.json
Normal file
9
packages/plugins/snapshot-field/package.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-snapshot-field",
|
||||
"version": "0.8.0-alpha.13",
|
||||
"main": "lib/server/index.js",
|
||||
"devDependencies": {
|
||||
"@nocobase/server": "0.8.0-alpha.13",
|
||||
"@nocobase/test": "0.8.0-alpha.13"
|
||||
}
|
||||
}
|
4
packages/plugins/snapshot-field/server.d.ts
vendored
Executable file
4
packages/plugins/snapshot-field/server.d.ts
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
export * from './lib/server';
|
||||
export { default } from './lib/server';
|
||||
|
30
packages/plugins/snapshot-field/server.js
Executable file
30
packages/plugins/snapshot-field/server.js
Executable file
@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
|
||||
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
||||
|
||||
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
||||
|
||||
var _index = _interopRequireWildcard(require("./lib/server"));
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
var _exportNames = {};
|
||||
Object.defineProperty(exports, "default", {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index.default;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(_index).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||
if (key in exports && exports[key] === _index[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function get() {
|
||||
return _index[key];
|
||||
}
|
||||
});
|
||||
});
|
@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { SchemaInitializer, gridRowColWrap } from '@nocobase/client';
|
||||
import { useSnapshotTranslation } from '../../locale';
|
||||
|
||||
export const SnapshotBlockInitializers = (props: any) => {
|
||||
const { t } = useSnapshotTranslation();
|
||||
const { insertPosition, component } = props;
|
||||
return (
|
||||
<SchemaInitializer.Button
|
||||
wrap={gridRowColWrap}
|
||||
insertPosition={insertPosition}
|
||||
component={component}
|
||||
title={component ? null : t('Add block')}
|
||||
icon={'PlusOutlined'}
|
||||
items={[
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Current record blocks")}}',
|
||||
children: [
|
||||
{
|
||||
key: 'details',
|
||||
type: 'item',
|
||||
title: '{{t("Details")}}',
|
||||
component: 'SnapshotBlockInitializersDetailItem',
|
||||
actionInitializers: 'CalendarFormActionInitializers',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: '{{t("Other blocks")}}',
|
||||
children: [
|
||||
{
|
||||
key: 'markdown',
|
||||
type: 'item',
|
||||
title: '{{t("Markdown")}}',
|
||||
component: 'MarkdownBlockInitializer',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
// const { t } = useSnapshotTranslation();
|
||||
// return (
|
||||
// <SchemaInitializer.Button
|
||||
// wrap={gridRowColWrap}
|
||||
// title={t('Add block')}
|
||||
// icon={'PlusOutlined'}
|
||||
// items={[
|
||||
// {
|
||||
// type: 'itemGroup',
|
||||
// title: '{{ t("Data blocks") }}',
|
||||
// children: [
|
||||
// {
|
||||
// type: 'item',
|
||||
// title: '{{t("Details")}}',
|
||||
// component: 'SnapshotBlockInitializersDetailItem',
|
||||
// actionInitializers: 'CalendarFormActionInitializers',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// type: 'itemGroup',
|
||||
// title: '{{t("Other blocks")}}',
|
||||
// children: [
|
||||
// {
|
||||
// type: 'item',
|
||||
// title: '{{t("Markdown")}}',
|
||||
// component: 'MarkdownBlockInitializer',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ]}
|
||||
// />
|
||||
// );
|
||||
};
|
@ -0,0 +1,121 @@
|
||||
import React from 'react';
|
||||
import { FormOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
SchemaInitializer,
|
||||
useBlockAssociationContext,
|
||||
useCollection,
|
||||
useSchemaTemplateManager,
|
||||
useRecordCollectionDataSourceItems,
|
||||
useBlockRequestContext,
|
||||
} from '@nocobase/client';
|
||||
import { ISchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export const createSnapshotBlockSchema = (options) => {
|
||||
const {
|
||||
formItemInitializers = 'ReadPrettyFormItemInitializers',
|
||||
actionInitializers = 'ReadPrettyFormActionInitializers',
|
||||
collection,
|
||||
association,
|
||||
resource,
|
||||
template,
|
||||
...others
|
||||
} = options;
|
||||
const resourceName = resource || association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${resourceName}:get`,
|
||||
'x-decorator': 'SnapshotBlockProvider',
|
||||
'x-decorator-props': {
|
||||
resource: resourceName,
|
||||
collection,
|
||||
association,
|
||||
readPretty: true,
|
||||
action: 'get',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
...others,
|
||||
},
|
||||
'x-designer': 'FormV2.ReadPrettyDesigner',
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-read-pretty': true,
|
||||
'x-component-props': {
|
||||
useProps: '{{ useFormBlockProps }}',
|
||||
},
|
||||
properties: {
|
||||
grid: template || {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': formItemInitializers,
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
console.log(JSON.stringify(schema, null, 2));
|
||||
return schema;
|
||||
};
|
||||
|
||||
export const SnapshotBlockInitializersDetailItem = (props) => {
|
||||
const {
|
||||
onCreateBlockSchema,
|
||||
componentType,
|
||||
createBlockSchema,
|
||||
insert,
|
||||
icon = true,
|
||||
targetCollection,
|
||||
...others
|
||||
} = props;
|
||||
const { getTemplateSchemaByMode } = useSchemaTemplateManager();
|
||||
const collection = targetCollection || useCollection();
|
||||
const association = useBlockAssociationContext();
|
||||
const { block } = useBlockRequestContext();
|
||||
const actionInitializers =
|
||||
block !== 'TableField' ? props.actionInitializers || 'ReadPrettyFormActionInitializers' : null;
|
||||
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={icon && <FormOutlined />}
|
||||
{...others}
|
||||
key={'snapshotDetail'}
|
||||
onClick={async ({ item }) => {
|
||||
if (item.template) {
|
||||
const s = await getTemplateSchemaByMode(item);
|
||||
if (item.template.componentName === 'ReadPrettyFormItem') {
|
||||
const blockSchema = createSnapshotBlockSchema({
|
||||
actionInitializers,
|
||||
association,
|
||||
collection: collection.name,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
template: s,
|
||||
});
|
||||
if (item.mode === 'reference') {
|
||||
blockSchema['x-template-key'] = item.template.key;
|
||||
}
|
||||
insert(blockSchema);
|
||||
} else {
|
||||
insert(s);
|
||||
}
|
||||
} else {
|
||||
insert(
|
||||
createSnapshotBlockSchema({
|
||||
actionInitializers,
|
||||
association,
|
||||
collection: collection.name,
|
||||
action: 'get',
|
||||
useSourceId: '{{ useSourceIdFromParentRecord }}',
|
||||
useParams: '{{ useParamsFromRecord }}',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}}
|
||||
items={useRecordCollectionDataSourceItems('ReadPrettyFormItem')}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,105 @@
|
||||
import React, { useContext, useMemo, useRef } from 'react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { useField } from '@formily/react';
|
||||
import {
|
||||
BlockAssociationContext,
|
||||
BlockRequestContext,
|
||||
BlockResourceContext,
|
||||
FormBlockContext,
|
||||
MaybeCollectionProvider,
|
||||
RecordProvider,
|
||||
useBlockRequestContext,
|
||||
useBlockResource,
|
||||
useCollectionManager,
|
||||
useDesignable,
|
||||
useRecord,
|
||||
useResource,
|
||||
} from '@nocobase/client';
|
||||
import { Spin } from 'antd';
|
||||
|
||||
const InternalFormBlockProvider = (props) => {
|
||||
const { action, readPretty } = props;
|
||||
const field = useField();
|
||||
const form = useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
readPretty,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const { resource, service } = useBlockRequestContext();
|
||||
const formBlockRef = useRef();
|
||||
if (service.loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
return (
|
||||
<FormBlockContext.Provider
|
||||
value={{
|
||||
action,
|
||||
form,
|
||||
field,
|
||||
service,
|
||||
resource,
|
||||
updateAssociationValues: [],
|
||||
formBlockRef,
|
||||
}}
|
||||
>
|
||||
{readPretty ? (
|
||||
<RecordProvider record={service?.data?.data}>
|
||||
<div ref={formBlockRef}>{props.children}</div>
|
||||
</RecordProvider>
|
||||
) : (
|
||||
<div ref={formBlockRef}>{props.children}</div>
|
||||
)}
|
||||
</FormBlockContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const BlockRequestProvider = (props) => {
|
||||
const field = useField();
|
||||
const resource = useBlockResource();
|
||||
const service = {
|
||||
loading: false,
|
||||
data: {
|
||||
data: useRecord(),
|
||||
},
|
||||
};
|
||||
const __parent = useContext(BlockRequestContext);
|
||||
return (
|
||||
<BlockRequestContext.Provider value={{ block: props.block, props, field, service, resource, __parent }}>
|
||||
{props.children}
|
||||
</BlockRequestContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const BlockProvider = (props) => {
|
||||
const { collection, association } = props;
|
||||
const resource = useResource(props);
|
||||
|
||||
return (
|
||||
<MaybeCollectionProvider collection={collection}>
|
||||
<BlockAssociationContext.Provider value={association}>
|
||||
<BlockResourceContext.Provider value={resource}>
|
||||
<BlockRequestProvider {...props}>{props.children}</BlockRequestProvider>
|
||||
</BlockResourceContext.Provider>
|
||||
</BlockAssociationContext.Provider>
|
||||
</MaybeCollectionProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const SnapshotBlockProvider = (props) => {
|
||||
const record = useRecord();
|
||||
const { __tableName } = record;
|
||||
const { getInheritCollections } = useCollectionManager();
|
||||
const inheritCollections = getInheritCollections(__tableName);
|
||||
const { designable } = useDesignable();
|
||||
const flag =
|
||||
!designable && __tableName && !inheritCollections.includes(props.collection) && __tableName !== props.collection;
|
||||
return (
|
||||
!flag && (
|
||||
<BlockProvider {...props}>
|
||||
<InternalFormBlockProvider {...props} />
|
||||
</BlockProvider>
|
||||
)
|
||||
);
|
||||
};
|
@ -0,0 +1,42 @@
|
||||
import { CollectionManagerContext, useHistoryCollectionsByNames } from '@nocobase/client';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
export const SnapshotHistoryCollectionProvider: React.FC<{ collectionName: string }> = (props) => {
|
||||
const { collectionName } = props;
|
||||
const { collections: allCollections, ...rest } = useContext(CollectionManagerContext);
|
||||
|
||||
// 目标表
|
||||
const snapshotTargetCollection = useHistoryCollectionsByNames([collectionName])?.[0];
|
||||
// 目标如果是继承表则获取继承表
|
||||
const inheritCollections = useHistoryCollectionsByNames(snapshotTargetCollection?.inherits ?? []);
|
||||
// 目标表内关联字段的表
|
||||
const associationFieldTargetCollections = useHistoryCollectionsByNames(
|
||||
snapshotTargetCollection?.fields.filter((i) => i.interface !== 'snapshot').map((i) => i.target) ?? [],
|
||||
);
|
||||
|
||||
// 替换表的集合
|
||||
const finallyHistoryCollecionts = [
|
||||
snapshotTargetCollection,
|
||||
...associationFieldTargetCollections,
|
||||
...inheritCollections,
|
||||
].filter((i) => i);
|
||||
|
||||
// 过滤出不需要替换的表
|
||||
const filterdAllCollection = allCollections.filter(
|
||||
(c) => !finallyHistoryCollecionts.map((i) => i.name).includes(c.name),
|
||||
);
|
||||
|
||||
// 最终替换后的表
|
||||
const overridedCollections = [...filterdAllCollection, ...finallyHistoryCollecionts];
|
||||
|
||||
return (
|
||||
<CollectionManagerContext.Provider
|
||||
value={{
|
||||
...rest,
|
||||
collections: overridedCollections,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</CollectionManagerContext.Provider>
|
||||
);
|
||||
};
|
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { connect, mapReadPretty, useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
InputRecordPicker,
|
||||
ReadPrettyRecordPicker,
|
||||
useActionContext,
|
||||
useCollection,
|
||||
useCollectionHistory,
|
||||
} from '@nocobase/client';
|
||||
import { SnapshotHistoryCollectionProvider } from './SnapshotHistoryCollectionProvider';
|
||||
|
||||
const useSnapshotFieldTargetCollectionName = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getField } = useCollection();
|
||||
const collectionField = getField(fieldSchema.name);
|
||||
const { historyCollections } = useCollectionHistory();
|
||||
return historyCollections.find((i) => i.name === collectionField.target)?.name;
|
||||
};
|
||||
|
||||
const ReadPrettyRecordPickerWrapper = (props) => {
|
||||
const collectionName = useSnapshotFieldTargetCollectionName();
|
||||
|
||||
return (
|
||||
<SnapshotHistoryCollectionProvider collectionName={collectionName}>
|
||||
<ReadPrettyRecordPicker {...props} />
|
||||
</SnapshotHistoryCollectionProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const SnapshotRecordPickerInner: any = connect(
|
||||
(props) => {
|
||||
const actionCtx = useActionContext();
|
||||
|
||||
const isUpdateAction = actionCtx.fieldSchema['x-action'] === 'update';
|
||||
|
||||
return isUpdateAction ? <ReadPrettyRecordPickerWrapper {...props} /> : <InputRecordPicker {...props} />;
|
||||
},
|
||||
// mapProps(mapSuffixProps),
|
||||
mapReadPretty(ReadPrettyRecordPickerWrapper),
|
||||
);
|
||||
|
||||
export const SnapshotRecordPicker = (props) => {
|
||||
const { value, onChange, ...restProps } = props;
|
||||
|
||||
const newProps = {
|
||||
...restProps,
|
||||
value: value?.data,
|
||||
onChange: (value) => onChange({ data: value }),
|
||||
};
|
||||
|
||||
return <SnapshotRecordPickerInner {...newProps} />;
|
||||
};
|
50
packages/plugins/snapshot-field/src/client/index.tsx
Normal file
50
packages/plugins/snapshot-field/src/client/index.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
CollectionHistoryProvider,
|
||||
CollectionManagerProvider,
|
||||
registerField,
|
||||
SchemaComponentOptions,
|
||||
SchemaInitializerContext,
|
||||
SchemaInitializerProvider,
|
||||
} from '@nocobase/client';
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import { useSnapshotInterface } from './interface';
|
||||
import { SnapshotRecordPicker } from './SnapshotRecordPicker';
|
||||
import { SnapshotBlockInitializers } from './SnapshotBlock/SnapshotBlockInitializers/SnapshotBlockInitializers';
|
||||
import { SnapshotBlockInitializersDetailItem } from './SnapshotBlock/SnapshotBlockInitializers/SnapshotBlockInitializersDetailItem';
|
||||
import { SnapshotBlockProvider } from './SnapshotBlock/SnapshotBlockProvider';
|
||||
|
||||
export default React.memo((props) => {
|
||||
const initializers = useContext(SchemaInitializerContext);
|
||||
const snapshot = useSnapshotInterface();
|
||||
|
||||
useEffect(() => {
|
||||
registerField(snapshot.group, snapshot.name as string, snapshot);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CollectionManagerProvider
|
||||
interfaces={{
|
||||
snapshot,
|
||||
}}
|
||||
>
|
||||
<CollectionHistoryProvider>
|
||||
<SchemaInitializerProvider
|
||||
initializers={{
|
||||
...initializers,
|
||||
SnapshotBlockInitializers,
|
||||
}}
|
||||
>
|
||||
<SchemaComponentOptions
|
||||
components={{
|
||||
SnapshotRecordPicker,
|
||||
SnapshotBlockProvider,
|
||||
SnapshotBlockInitializersDetailItem,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SchemaComponentOptions>
|
||||
</SchemaInitializerProvider>
|
||||
</CollectionHistoryProvider>
|
||||
</CollectionManagerProvider>
|
||||
);
|
||||
});
|
102
packages/plugins/snapshot-field/src/client/interface.ts
Normal file
102
packages/plugins/snapshot-field/src/client/interface.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { IField } from '@nocobase/client';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { interfacesProperties } from '@nocobase/client';
|
||||
import { useSnapshotTranslation } from './locale';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const { defaultProps, recordPickerSelector } = interfacesProperties;
|
||||
|
||||
export const useSnapshotInterface = () => {
|
||||
const { t } = useSnapshotTranslation();
|
||||
|
||||
const recordPickerViewer = {
|
||||
type: 'void',
|
||||
title: t('View record'),
|
||||
'x-component': 'RecordPicker.Viewer',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
// 'x-initializer': 'TabPaneInitializers',
|
||||
properties: {
|
||||
tab1: {
|
||||
type: 'void',
|
||||
title: t('Detail'),
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'SnapshotBlockInitializers',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const snapshot: IField = {
|
||||
name: 'snapshot',
|
||||
type: 'object',
|
||||
group: 'advanced',
|
||||
title: t('Snapshot'),
|
||||
description: t('Snapshot to description'),
|
||||
default: {
|
||||
type: 'snapshot',
|
||||
// name,
|
||||
uiSchema: {
|
||||
// title,
|
||||
'x-component': 'SnapshotRecordPicker',
|
||||
'x-component-props': {
|
||||
// mode: 'tags',
|
||||
multiple: true,
|
||||
fieldNames: {
|
||||
label: 'id',
|
||||
value: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
schemaInitialize(schema: ISchema, { field, readPretty, action, block }) {
|
||||
if (readPretty || action === 'update') {
|
||||
schema['properties'] = {
|
||||
viewer: cloneDeep(recordPickerViewer),
|
||||
};
|
||||
} else {
|
||||
schema['properties'] = {
|
||||
selector: cloneDeep(recordPickerSelector),
|
||||
};
|
||||
}
|
||||
},
|
||||
initialize: (values: any) => {},
|
||||
properties: {
|
||||
...defaultProps,
|
||||
target: {
|
||||
type: 'string',
|
||||
title: t('Related collection'),
|
||||
required: true,
|
||||
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-disabled': '{{ !createOnly }}',
|
||||
},
|
||||
'uiSchema.x-component-props.multiple': {
|
||||
type: 'boolean',
|
||||
'x-content': t('Allow linking to multiple records'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return useMemo<IField>(() => snapshot, [t]);
|
||||
};
|
10
packages/plugins/snapshot-field/src/client/locale/en-US.ts
Normal file
10
packages/plugins/snapshot-field/src/client/locale/en-US.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export default {
|
||||
Detail: 'Detail',
|
||||
Snapshot: 'Snapshot',
|
||||
'View record': 'View record',
|
||||
'Add block': 'Add block',
|
||||
'Allow linking to multiple records': 'Allow linking to multiple records',
|
||||
'Snapshot to description':
|
||||
'It is used to create a snapshot of the table, save the current data of the pointed table, and save it only when the record to which it belongs is created, and will not be updated later.',
|
||||
'Related collection': 'Related collection',
|
||||
};
|
24
packages/plugins/snapshot-field/src/client/locale/index.ts
Normal file
24
packages/plugins/snapshot-field/src/client/locale/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { i18n } from '@nocobase/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import enUS from './en-US';
|
||||
import jaJP from './ja-JP';
|
||||
import ruRU from './ru-RU';
|
||||
import trTR from './tr-TR';
|
||||
import zhCN from './zh-CN';
|
||||
|
||||
export const NAMESPACE = 'snapshot-field';
|
||||
|
||||
i18n.addResources('zh-CN', NAMESPACE, zhCN);
|
||||
i18n.addResources('en-US', NAMESPACE, enUS);
|
||||
i18n.addResources('ja-JP', NAMESPACE, jaJP);
|
||||
i18n.addResources('ru-RU', NAMESPACE, ruRU);
|
||||
i18n.addResources('tr-TR', NAMESPACE, trTR);
|
||||
|
||||
export function lang(key: string) {
|
||||
return i18n.t(key, { ns: NAMESPACE });
|
||||
}
|
||||
|
||||
export function useSnapshotTranslation() {
|
||||
return useTranslation(NAMESPACE);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export default {};
|
@ -0,0 +1 @@
|
||||
export default {};
|
@ -0,0 +1 @@
|
||||
export default {};
|
@ -0,0 +1,9 @@
|
||||
export default {
|
||||
Detail: '详情',
|
||||
Snapshot: '快照',
|
||||
'Add block': '创建区块',
|
||||
'Snapshot to description': '用于创建表的快照,保存指向的表的当前数据,仅在其所属的记录创建时保存,后续不再更新。',
|
||||
'View record': '查看数据',
|
||||
'Allow linking to multiple records': '允许关联多条记录',
|
||||
'Related collection': '关系表',
|
||||
};
|
1
packages/plugins/snapshot-field/src/index.ts
Normal file
1
packages/plugins/snapshot-field/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './server';
|
@ -0,0 +1,55 @@
|
||||
import { CollectionOptions } from '@nocobase/database';
|
||||
|
||||
export default {
|
||||
name: 'collectionsHistory',
|
||||
title: '数据表历史',
|
||||
sortable: 'sort',
|
||||
autoGenId: false,
|
||||
model: 'CollectionModel',
|
||||
repository: 'CollectionRepository',
|
||||
timestamps: false,
|
||||
filterTargetKey: 'name',
|
||||
fields: [
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'key',
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'name',
|
||||
unique: true,
|
||||
prefix: 't_',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'inherit',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
name: 'hidden',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
type: 'json',
|
||||
name: 'options',
|
||||
defaultValue: {},
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'fields',
|
||||
target: 'fieldsHistory',
|
||||
sourceKey: 'name',
|
||||
targetKey: 'name',
|
||||
foreignKey: 'collectionName',
|
||||
onDelete: 'CASCADE',
|
||||
sortBy: 'sort',
|
||||
},
|
||||
],
|
||||
} as CollectionOptions;
|
@ -0,0 +1,73 @@
|
||||
import { CollectionOptions } from '@nocobase/database';
|
||||
|
||||
export default {
|
||||
name: 'fieldsHistory',
|
||||
title: '{{t("Fields history")}}',
|
||||
autoGenId: false,
|
||||
model: 'FieldModel',
|
||||
timestamps: false,
|
||||
sortable: {
|
||||
name: 'sort',
|
||||
scopeKey: 'collectionName',
|
||||
},
|
||||
indexes: [
|
||||
{
|
||||
type: 'UNIQUE',
|
||||
fields: ['collectionName', 'name'],
|
||||
},
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'key',
|
||||
primaryKey: true,
|
||||
},
|
||||
{
|
||||
type: 'uid',
|
||||
name: 'name',
|
||||
prefix: 'f_',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'type',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'interface',
|
||||
allowNull: true,
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'collection',
|
||||
target: 'collectionsHistory',
|
||||
foreignKey: 'collectionName',
|
||||
targetKey: 'name',
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
{
|
||||
type: 'hasMany',
|
||||
name: 'children',
|
||||
target: 'fieldsHistory',
|
||||
sourceKey: 'key',
|
||||
foreignKey: 'parentKey',
|
||||
},
|
||||
{
|
||||
type: 'hasOne',
|
||||
name: 'reverseField',
|
||||
target: 'fieldsHistory',
|
||||
sourceKey: 'key',
|
||||
foreignKey: 'reverseKey',
|
||||
},
|
||||
{
|
||||
type: 'belongsTo',
|
||||
name: 'uiSchema',
|
||||
target: 'uiSchemas',
|
||||
foreignKey: 'uiSchemaUid',
|
||||
},
|
||||
{
|
||||
type: 'json',
|
||||
name: 'options',
|
||||
defaultValue: {},
|
||||
},
|
||||
],
|
||||
} as CollectionOptions;
|
@ -0,0 +1,12 @@
|
||||
import { Field, BaseColumnFieldOptions } from '@nocobase/database';
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
export class SnapshotField extends Field {
|
||||
get dataType() {
|
||||
return DataTypes.JSON;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SnapshotFieldOptions extends BaseColumnFieldOptions {
|
||||
type: 'snapshot';
|
||||
}
|
1
packages/plugins/snapshot-field/src/server/index.ts
Normal file
1
packages/plugins/snapshot-field/src/server/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './plugin';
|
108
packages/plugins/snapshot-field/src/server/plugin.ts
Normal file
108
packages/plugins/snapshot-field/src/server/plugin.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { Model } from '@nocobase/database';
|
||||
import { InstallOptions, Plugin } from '@nocobase/server';
|
||||
import { resolve } from 'path';
|
||||
import { SnapshotField } from './fields/snapshot-field';
|
||||
|
||||
export class SnapshotFieldPlugin extends Plugin {
|
||||
afterAdd() {}
|
||||
|
||||
async beforeLoad() {
|
||||
const collectionHandler = async (model: Model, { transaction }) => {
|
||||
const collectionDoc = model.toJSON();
|
||||
const collectionsHistoryRepository = this.app.db.getRepository('collectionsHistory');
|
||||
const fieldsHistoryRepository = this.app.db.getRepository('fieldsHistory');
|
||||
|
||||
const existCollection: Model = await collectionsHistoryRepository.findOne({
|
||||
filter: {
|
||||
name: collectionDoc.name,
|
||||
},
|
||||
});
|
||||
|
||||
if (existCollection) {
|
||||
// 删除表和其关联字段
|
||||
await existCollection.destroy({
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
await collectionsHistoryRepository.create({
|
||||
values: collectionDoc,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await fieldsHistoryRepository.createMany({
|
||||
records: collectionDoc.fields ?? [],
|
||||
transaction,
|
||||
});
|
||||
};
|
||||
|
||||
this.app.db.on('collections.afterCreateWithAssociations', collectionHandler);
|
||||
|
||||
const fieldHandler = async (model: Model, { transaction }) => {
|
||||
const fieldDoc = model.get();
|
||||
const fieldsHistoryRepository = this.app.db.getRepository('fieldsHistory');
|
||||
const existField: Model = await fieldsHistoryRepository.findOne({
|
||||
filter: {
|
||||
name: fieldDoc.name,
|
||||
},
|
||||
});
|
||||
if (existField) {
|
||||
await existField.destroy({
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
await fieldsHistoryRepository.create({
|
||||
values: fieldDoc,
|
||||
transaction,
|
||||
});
|
||||
};
|
||||
|
||||
this.app.db.on('fields.afterCreateWithAssociations', fieldHandler);
|
||||
}
|
||||
|
||||
async load() {
|
||||
// 导入 collection
|
||||
await this.db.import({
|
||||
directory: resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
this.app.db.registerFieldTypes({
|
||||
snapshot: SnapshotField,
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化安装的时候
|
||||
async install(options?: InstallOptions) {
|
||||
await this.app.db.sequelize.transaction(async (transaction) => {
|
||||
const collectionsRepository = this.app.db.getRepository('collections');
|
||||
const collectionsHistoryRepository = this.app.db.getRepository('collectionsHistory');
|
||||
|
||||
if ((await collectionsHistoryRepository.find()).length === 0) {
|
||||
const collectionsModels: Model[] = await collectionsRepository.find();
|
||||
await collectionsHistoryRepository.createMany({
|
||||
records: collectionsModels.map((m) => m.get()),
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
const fieldsRepository = this.app.db.getRepository('fields');
|
||||
const fieldsHistoryRepository = this.app.db.getRepository('fieldsHistory');
|
||||
|
||||
if ((await fieldsHistoryRepository.find()).length === 0) {
|
||||
const fieldsModels: Model[] = await fieldsRepository.find();
|
||||
await fieldsHistoryRepository.createMany({
|
||||
records: fieldsModels.map((m) => m.get()),
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async afterEnable() {}
|
||||
|
||||
async afterDisable() {}
|
||||
|
||||
async remove() {}
|
||||
}
|
||||
|
||||
export default SnapshotFieldPlugin;
|
@ -31,6 +31,7 @@
|
||||
"@nocobase/plugin-verification": "0.8.0-alpha.13",
|
||||
"@nocobase/plugin-workflow": "0.8.0-alpha.13",
|
||||
"@nocobase/plugin-map": "0.8.0-alpha.13",
|
||||
"@nocobase/plugin-snapshot-field": "0.8.0-alpha.13",
|
||||
"@nocobase/plugin-iframe-block": "0.8.0-alpha.13",
|
||||
"@nocobase/server": "0.8.0-alpha.13"
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ export class PresetNocoBase extends Plugin {
|
||||
}
|
||||
|
||||
getLocalPlugins() {
|
||||
const localPlugins = ['sample-hello', 'oidc', 'saml', 'map'];
|
||||
const localPlugins = ['sample-hello', 'oidc', 'saml', 'map', 'snapshot-field'];
|
||||
return localPlugins;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user