mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-12-06 06:08:07 +08:00
feat(collection-manager): support for setting the title field (#1729)
* feat: configure fields * feat: edit * feat: enable delete action * style: indent style * feat: support to override and view inherited fields * feat: support to delete and add field and sync * fix: should delete the seleted option * feat: support to setting title field * feat: avoid infinite loops and add default title field * feat(data-templates): use titleField to display title * fix: fix the pagination of collection manager * feat: add prompt for title field switch * feat: filter field types * feat: not use title switch when field interface is icon * feat: translate * fix: sync update collections data * feat: translate * fix: fix build error --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
5930218811
commit
3c884cdd36
@ -10,7 +10,7 @@ import * as defaultInterfaces from './interfaces';
|
||||
import { CollectionManagerOptions } from './types';
|
||||
|
||||
export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (props) => {
|
||||
const { service, interfaces, collections = [], refreshCM, templates } = props;
|
||||
const { service, interfaces, collections = [], refreshCM, updateCollection, templates } = props;
|
||||
const defaultTemplates = keyBy(templateOptions(), (item) => item.name);
|
||||
const ctx = useContext(CollectionManagerContext);
|
||||
return (
|
||||
@ -22,6 +22,7 @@ export const CollectionManagerProvider: React.FC<CollectionManagerOptions> = (pr
|
||||
templates: { ...defaultTemplates, ...templates },
|
||||
collections: [...ctx.collections, ...collections],
|
||||
refreshCM,
|
||||
updateCollection,
|
||||
}}
|
||||
>
|
||||
<CollectionManagerSchemaComponentProvider>{props.children}</CollectionManagerSchemaComponentProvider>
|
||||
@ -76,12 +77,17 @@ export const RemoteCollectionManagerProvider = (props: any) => {
|
||||
return data?.data || [];
|
||||
};
|
||||
|
||||
const updateCollection = (collection) => {
|
||||
service.mutate({ data: collection });
|
||||
};
|
||||
|
||||
return (
|
||||
<CollectionCategroriesProvider service={{ ...result }} refreshCategory={refreshCategory}>
|
||||
<CollectionManagerProvider
|
||||
service={{ ...service, contentLoading, setContentLoading }}
|
||||
collections={service?.data?.data}
|
||||
refreshCM={refreshCM}
|
||||
updateCollection={updateCollection}
|
||||
{...props}
|
||||
/>
|
||||
</CollectionCategroriesProvider>
|
||||
@ -95,7 +101,7 @@ export const CollectionCategroriesProvider = (props) => {
|
||||
value={{
|
||||
data: service?.data?.data,
|
||||
refresh: refreshCategory,
|
||||
...props
|
||||
...props,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
@ -0,0 +1,440 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Field, createForm } from '@formily/core';
|
||||
import { FieldContext, FormContext, useField } from '@formily/react';
|
||||
import { Space, Switch, Table, TableColumnProps, Tag, Tooltip } from 'antd';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { Action, useAttach, useCompile } from '../../schema-component';
|
||||
import {
|
||||
ResourceActionContext,
|
||||
ResourceActionProvider,
|
||||
useResourceActionContext,
|
||||
useResourceContext,
|
||||
} from '../ResourceActionProvider';
|
||||
import {
|
||||
isDeleteButtonDisabled,
|
||||
useBulkDestroyActionAndRefreshCM,
|
||||
useDestroyActionAndRefreshCM,
|
||||
} from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks/useCollectionManager';
|
||||
import { AddCollectionField } from './AddFieldAction';
|
||||
import { EditCollectionField } from './EditFieldAction';
|
||||
import { OverridingCollectionField } from './OverridingCollectionField';
|
||||
import { SyncFieldsAction } from './SyncFieldsAction';
|
||||
import { ViewCollectionField } from './ViewInheritedField';
|
||||
import { collection } from './schemas/collectionFields';
|
||||
const CELL_WIDTH = 200;
|
||||
|
||||
const indentStyle = css`
|
||||
.ant-table {
|
||||
margin-left: -7px !important;
|
||||
}
|
||||
`;
|
||||
const rowStyle = css`
|
||||
.ant-table-cell {
|
||||
background-color: white;
|
||||
}
|
||||
`;
|
||||
|
||||
const titlePrompt = 'Default title for each record';
|
||||
// 只有下面类型的字段才可以设置为标题字段
|
||||
const expectTypes = ['string', 'integer', 'bigInt', 'float', 'double', 'decimal', 'date', 'dateonly', 'time'];
|
||||
const excludeInterfaces = ['icon'];
|
||||
|
||||
const CurrentFields = (props) => {
|
||||
const compile = useCompile();
|
||||
const { getInterface } = useCollectionManager();
|
||||
const { t } = useTranslation();
|
||||
const { setState } = useResourceActionContext();
|
||||
const { resource, targetKey } = props.collectionResource || {};
|
||||
const { [targetKey]: filterByTk, titleField } = useRecord();
|
||||
const [loadingRecord, setLoadingRecord] = React.useState<any>(null);
|
||||
const { updateCollection } = useCollectionManager();
|
||||
|
||||
const columns: TableColumnProps<any>[] = [
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value) => <div style={{ marginLeft: 7 }}>{compile(value)}</div>,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: t('Field name'),
|
||||
width: CELL_WIDTH + 20,
|
||||
},
|
||||
{
|
||||
dataIndex: 'interface',
|
||||
title: t('Field interface'),
|
||||
width: CELL_WIDTH,
|
||||
render: (value) => <Tag>{compile(getInterface(value)?.title)}</Tag>,
|
||||
},
|
||||
{
|
||||
dataIndex: 'titleField',
|
||||
title: t('Title field'),
|
||||
width: CELL_WIDTH,
|
||||
render: function Render(_, record) {
|
||||
const handleChange = (checked) => {
|
||||
setLoadingRecord(record);
|
||||
resource
|
||||
.update({ filterByTk, values: { titleField: checked ? record.name : 'id' } })
|
||||
.then(async () => {
|
||||
const data = await props.refreshAsync();
|
||||
if (data?.data) {
|
||||
updateCollection(data.data);
|
||||
}
|
||||
setLoadingRecord(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoadingRecord(null);
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
return expectTypes.includes(record.type) && !excludeInterfaces.includes(record.interface) ? (
|
||||
<Tooltip title={t(titlePrompt)} placement="right" overlayInnerStyle={{ textAlign: 'center' }}>
|
||||
<Switch
|
||||
size="small"
|
||||
loading={record.name === loadingRecord?.name}
|
||||
checked={record.name === (titleField || 'id')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'actions',
|
||||
title: t('Actions'),
|
||||
width: CELL_WIDTH,
|
||||
render: (_, record) => {
|
||||
const deleteProps = {
|
||||
confirm: {
|
||||
title: t('Delete record'),
|
||||
content: t('Are you sure you want to delete it?'),
|
||||
},
|
||||
useAction: useDestroyActionAndRefreshCM,
|
||||
disabled: isDeleteButtonDisabled(record),
|
||||
title: t('Delete'),
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordProvider record={record}>
|
||||
<Space>
|
||||
<EditCollectionField type="primary" />
|
||||
<Action.Link {...deleteProps} />
|
||||
</Space>
|
||||
</RecordProvider>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Table
|
||||
rowKey={'name'}
|
||||
columns={columns}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
dataSource={props.fields}
|
||||
rowSelection={{
|
||||
type: 'checkbox',
|
||||
onChange: (selectedRowKeys) => {
|
||||
setState((state) => {
|
||||
const result = [...(state.selectedRowKeys || []), ...selectedRowKeys];
|
||||
return {
|
||||
...state,
|
||||
selectedRowKeys: result,
|
||||
};
|
||||
});
|
||||
},
|
||||
}}
|
||||
className={indentStyle}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const InheritFields = (props) => {
|
||||
const compile = useCompile();
|
||||
const { getInterface } = useCollectionManager();
|
||||
const { resource, targetKey } = props.collectionResource || {};
|
||||
const { [targetKey]: filterByTk, titleField, name } = useRecord();
|
||||
const [loadingRecord, setLoadingRecord] = React.useState(null);
|
||||
const { t } = useTranslation();
|
||||
const { updateCollection } = useCollectionManager();
|
||||
|
||||
const columns: TableColumnProps<any>[] = [
|
||||
{
|
||||
dataIndex: ['uiSchema', 'title'],
|
||||
title: t('Field display name'),
|
||||
render: (value) => <div style={{ marginLeft: 1 }}>{compile(value)}</div>,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: t('Field name'),
|
||||
width: CELL_WIDTH + 20,
|
||||
},
|
||||
{
|
||||
dataIndex: 'interface',
|
||||
title: t('Field interface'),
|
||||
width: CELL_WIDTH,
|
||||
render: (value) => <Tag>{compile(getInterface(value)?.title)}</Tag>,
|
||||
},
|
||||
{
|
||||
dataIndex: 'titleField',
|
||||
title: t('Title field'),
|
||||
width: CELL_WIDTH,
|
||||
render(_, record) {
|
||||
const handleChange = (checked) => {
|
||||
setLoadingRecord(record);
|
||||
resource
|
||||
.update({ filterByTk, values: { titleField: checked ? record.name : 'id' } })
|
||||
.then(async () => {
|
||||
const data = await props.refreshAsync();
|
||||
if (data?.data) {
|
||||
updateCollection(data.data);
|
||||
}
|
||||
setLoadingRecord(null);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoadingRecord(null);
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
|
||||
return expectTypes.includes(record.type) && !excludeInterfaces.includes(record.interface) ? (
|
||||
<Tooltip title={t(titlePrompt)} placement="right" overlayInnerStyle={{ textAlign: 'center' }}>
|
||||
<Switch
|
||||
size="small"
|
||||
loading={record.name === loadingRecord?.name}
|
||||
checked={record.name === (titleField || 'id')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'actions',
|
||||
title: t('Actions'),
|
||||
width: CELL_WIDTH,
|
||||
render: function Render(_, record) {
|
||||
const overrideProps = {
|
||||
type: 'primary',
|
||||
currentCollection: name,
|
||||
};
|
||||
const viewCollectionProps = {
|
||||
type: 'primary',
|
||||
};
|
||||
|
||||
return (
|
||||
<RecordProvider record={record}>
|
||||
<Space>
|
||||
<OverridingCollectionField {...overrideProps} />
|
||||
<ViewCollectionField {...viewCollectionProps} />
|
||||
</Space>
|
||||
</RecordProvider>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Table
|
||||
rowKey={'name'}
|
||||
columns={columns}
|
||||
showHeader={false}
|
||||
pagination={false}
|
||||
dataSource={props.fields.filter((field) => field.interface)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const CollectionFields = (props) => {
|
||||
const compile = useCompile();
|
||||
const field = useField<Field>();
|
||||
const { name } = useRecord();
|
||||
const { getInterface, getInheritCollections, getCollection, getCurrentCollectionFields } = useCollectionManager();
|
||||
const form = useMemo(() => createForm(), []);
|
||||
const f = useAttach(form.createArrayField({ ...field.props, basePath: '' }));
|
||||
const { t } = useTranslation();
|
||||
const collectionResource = useResourceContext();
|
||||
const { refreshAsync } = useContext(ResourceActionContext);
|
||||
|
||||
const inherits = getInheritCollections(name);
|
||||
|
||||
const columns: TableColumnProps<any>[] = [
|
||||
{
|
||||
dataIndex: 'title',
|
||||
title: t('Field display name'),
|
||||
render: (value) => (
|
||||
<div
|
||||
className={css`
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
width: 100px;
|
||||
`}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
),
|
||||
// width: CELL_WIDTH,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: t('Field name'),
|
||||
width: CELL_WIDTH + 20,
|
||||
},
|
||||
{
|
||||
dataIndex: 'interface',
|
||||
title: t('Field interface'),
|
||||
width: CELL_WIDTH,
|
||||
},
|
||||
{
|
||||
dataIndex: 'titleField',
|
||||
title: t('Title field'),
|
||||
width: CELL_WIDTH,
|
||||
},
|
||||
{
|
||||
dataIndex: 'actions',
|
||||
title: t('Actions'),
|
||||
width: CELL_WIDTH,
|
||||
},
|
||||
];
|
||||
|
||||
const fields = getCurrentCollectionFields(name);
|
||||
|
||||
const groups = {
|
||||
pf: [],
|
||||
association: [],
|
||||
general: [],
|
||||
system: [],
|
||||
};
|
||||
|
||||
fields.forEach((field) => {
|
||||
if (field.primaryKey || field.isForeignKey) {
|
||||
groups.pf.push(field);
|
||||
} else if (field.interface) {
|
||||
const conf = getInterface(field.interface);
|
||||
if (conf.group === 'systemInfo') {
|
||||
groups.system.push(field);
|
||||
} else if (['m2m', 'm2o', 'o2b', 'o2m', 'linkTo'].includes(field.interface)) {
|
||||
groups.association.push(field);
|
||||
} else {
|
||||
groups.general.push(field);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const dataSource = [
|
||||
{
|
||||
key: 'pf',
|
||||
title: t('PK & FK fields'),
|
||||
fields: groups.pf,
|
||||
},
|
||||
{
|
||||
key: 'association',
|
||||
title: t('Association fields'),
|
||||
fields: groups.association,
|
||||
},
|
||||
{
|
||||
key: 'general',
|
||||
title: t('General fields'),
|
||||
fields: groups.general,
|
||||
},
|
||||
{
|
||||
key: 'system',
|
||||
title: t('System fields'),
|
||||
fields: groups.system,
|
||||
},
|
||||
];
|
||||
|
||||
dataSource.push(
|
||||
...inherits.map((key) => {
|
||||
const collection = getCollection(key);
|
||||
return {
|
||||
key,
|
||||
title: `${t('Inherited fields')} - ` + compile(collection.title),
|
||||
inherit: true,
|
||||
fields: collection.fields,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const resourceActionProps = {
|
||||
association: {
|
||||
sourceKey: 'name',
|
||||
targetKey: 'name',
|
||||
},
|
||||
collection,
|
||||
request: {
|
||||
resource: 'collections.fields',
|
||||
action: 'list',
|
||||
params: {
|
||||
paginate: false,
|
||||
filter: {
|
||||
$or: [{ 'interface.$not': null }, { 'options.source.$notEmpty': true }],
|
||||
},
|
||||
sort: ['sort'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const deleteProps = useMemo(
|
||||
() => ({
|
||||
useAction: useBulkDestroyActionAndRefreshCM,
|
||||
title: t('Delete'),
|
||||
icon: 'DeleteOutlined',
|
||||
confirm: {
|
||||
title: t('Delete record'),
|
||||
content: t('Are you sure you want to delete it?'),
|
||||
},
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
const addProps = { type: 'primary' };
|
||||
const syncProps = { type: 'primary' };
|
||||
|
||||
return (
|
||||
<ResourceActionProvider {...resourceActionProps}>
|
||||
<FormContext.Provider value={form}>
|
||||
<FieldContext.Provider value={f}>
|
||||
<Space
|
||||
align={'end'}
|
||||
className={css`
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
`}
|
||||
>
|
||||
<Action {...deleteProps} />
|
||||
<SyncFieldsAction {...syncProps} />
|
||||
<AddCollectionField {...addProps} />
|
||||
</Space>
|
||||
<Table
|
||||
rowKey={'key'}
|
||||
columns={columns}
|
||||
dataSource={dataSource.filter((d) => d.fields.length)}
|
||||
pagination={false}
|
||||
expandable={{
|
||||
defaultExpandAllRows: true,
|
||||
expandedRowClassName: () => rowStyle,
|
||||
expandedRowRender: (record) =>
|
||||
record.inherit ? (
|
||||
<InheritFields
|
||||
fields={record.fields}
|
||||
collectionResource={collectionResource}
|
||||
refreshAsync={refreshAsync}
|
||||
/>
|
||||
) : (
|
||||
<CurrentFields
|
||||
fields={record.fields}
|
||||
collectionResource={collectionResource}
|
||||
refreshAsync={refreshAsync}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
</FormContext.Provider>
|
||||
</ResourceActionProvider>
|
||||
);
|
||||
};
|
@ -14,7 +14,7 @@ import {
|
||||
useCompile,
|
||||
useRecord,
|
||||
useRequest,
|
||||
useSchemaInitializer
|
||||
useSchemaInitializer,
|
||||
} from '../..';
|
||||
import { overridingSchema } from '../Configuration/schemas/collectionFields';
|
||||
|
||||
@ -192,10 +192,9 @@ export const CollectionFieldsTableArray: React.FC<any> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const expandedRowRender = (record: CategorizeDataItem, index, indent, expanded) => {
|
||||
if(!props.loading){
|
||||
const columns = useTableColumns();
|
||||
const ExpandedRowRender = (record: CategorizeDataItem, index, indent, expanded) => {
|
||||
const columns = useTableColumns();
|
||||
if (!props.loading) {
|
||||
if (inherits.includes(record.key)) {
|
||||
columns.pop();
|
||||
columns.push({
|
||||
@ -244,7 +243,6 @@ export const CollectionFieldsTableArray: React.FC<any> = observer((props) => {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
return (
|
||||
<div
|
||||
@ -262,7 +260,7 @@ export const CollectionFieldsTableArray: React.FC<any> = observer((props) => {
|
||||
dataSource={categorizeData}
|
||||
pagination={false}
|
||||
expandable={{
|
||||
expandedRowRender,
|
||||
expandedRowRender: ExpandedRowRender,
|
||||
expandedRowKeys: expandedKeys,
|
||||
}}
|
||||
onExpand={(expanded, record) => {
|
||||
|
@ -12,10 +12,11 @@ import { CollectionCategroriesContext } from '../context';
|
||||
import { useCollectionManager } from '../hooks/useCollectionManager';
|
||||
import { DataSourceContext } from '../sub-table';
|
||||
import { AddSubFieldAction } from './AddSubFieldAction';
|
||||
import { CollectionFields } from './CollectionFields';
|
||||
import { EditSubFieldAction } from './EditSubFieldAction';
|
||||
import { FieldSummary } from './components/FieldSummary';
|
||||
import { TemplateSummay } from './components/TemplateSummay';
|
||||
import { collectionSchema } from './schemas/collections';
|
||||
import {TemplateSummay} from './components/TemplateSummay';
|
||||
|
||||
/**
|
||||
* @param service
|
||||
@ -166,6 +167,7 @@ export const ConfigurationTable = () => {
|
||||
FieldSummary,
|
||||
TemplateSummay,
|
||||
CollectionFieldsTable,
|
||||
CollectionFields,
|
||||
}}
|
||||
scope={{
|
||||
useDestroySubField,
|
||||
|
@ -9,14 +9,15 @@ import {
|
||||
useSensor,
|
||||
useSensors
|
||||
} from '@dnd-kit/core';
|
||||
import { observer, RecursionField } from '@formily/react';
|
||||
import { RecursionField, observer } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { Badge, Card, Dropdown, Menu, Modal, Tabs } from 'antd';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useAPIClient } from '../../api-client';
|
||||
import { SchemaComponent, SchemaComponentOptions, useCompile } from '../../schema-component';
|
||||
import { CollectionCategroriesContext } from '../context';
|
||||
import { useResourceActionContext } from '../ResourceActionProvider';
|
||||
import { CollectionCategroriesContext } from '../context';
|
||||
import { CollectionFields } from './CollectionFields';
|
||||
import { collectionTableSchema } from './schemas/collections';
|
||||
|
||||
function Draggable(props) {
|
||||
@ -240,6 +241,7 @@ export const ConfigurationTabs = () => {
|
||||
>
|
||||
<Card bordered={false}>
|
||||
<SchemaComponentOptions
|
||||
components={{ CollectionFields }}
|
||||
inherit
|
||||
scope={{ loadCategories, categoryVisible: item.id === 'all', categoryId: item.id }}
|
||||
>
|
||||
|
@ -8,10 +8,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useRequest } from '../../api-client';
|
||||
import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import { useCancelAction } from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import { IField } from '../interfaces/types';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import * as components from './components';
|
||||
|
||||
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
||||
@ -26,10 +26,10 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
|
||||
name: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-disabled': true,
|
||||
// 'x-disabled': true,
|
||||
},
|
||||
};
|
||||
properties.name['x-disabled'] = true;
|
||||
// properties.name['x-disabled'] = true;
|
||||
if (schema.hasDefaultValue === true) {
|
||||
properties['defaultValue'] = cloneDeep(schema.default.uiSchema);
|
||||
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
|
||||
|
@ -8,10 +8,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient, useRequest } from '../../api-client';
|
||||
import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import { useCancelAction, useUpdateAction } from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import { IField } from '../interfaces/types';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import * as components from './components';
|
||||
|
||||
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
||||
@ -24,7 +24,7 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
|
||||
}
|
||||
|
||||
if (schema.hasDefaultValue === true) {
|
||||
properties['defaultValue'] = cloneDeep(schema.default.uiSchema)||{};
|
||||
properties['defaultValue'] = cloneDeep(schema.default.uiSchema) || {};
|
||||
properties['defaultValue']['title'] = compile('{{ t("Default value") }}');
|
||||
properties['defaultValue']['x-decorator'] = 'FormItem';
|
||||
properties['defaultValue']['x-reactions'] = {
|
||||
|
@ -8,10 +8,10 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient, useRequest } from '../../api-client';
|
||||
import { RecordProvider, useRecord } from '../../record-provider';
|
||||
import { ActionContext, SchemaComponent, useActionContext, useCompile } from '../../schema-component';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import { useCancelAction } from '../action-hooks';
|
||||
import { useCollectionManager } from '../hooks';
|
||||
import { IField } from '../interfaces/types';
|
||||
import { useResourceActionContext, useResourceContext } from '../ResourceActionProvider';
|
||||
import * as components from './components';
|
||||
|
||||
const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => {
|
||||
|
@ -2,7 +2,7 @@ import { ISchema } from '@formily/react';
|
||||
import { CollectionOptions } from '../../types';
|
||||
import { CollectionFieldInterface } from '../components/CollectionFieldInterface';
|
||||
|
||||
const collection: CollectionOptions = {
|
||||
export const collection: CollectionOptions = {
|
||||
name: 'fields',
|
||||
fields: [
|
||||
{
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { ISchema, Schema } from '@formily/react';
|
||||
import { message } from 'antd';
|
||||
import { uid } from '@formily/shared';
|
||||
import { message } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient } from '../../../api-client';
|
||||
import { i18n } from '../../../i18n';
|
||||
import { CollectionOptions } from '../../types';
|
||||
import { CollectionTemplate } from '../components/CollectionTemplate';
|
||||
import { CollectionCategory } from '../components/CollectionCategory';
|
||||
import { collectionFieldSchema } from './collectionFields';
|
||||
|
||||
import { CollectionTemplate } from '../components/CollectionTemplate';
|
||||
const compile = (source) => {
|
||||
return Schema.compile(source, { t: i18n.t });
|
||||
};
|
||||
@ -255,6 +253,7 @@ export const collectionTableSchema: ISchema = {
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
destroyOnClose: true,
|
||||
width: '70%',
|
||||
},
|
||||
'x-reactions': (field) => {
|
||||
const i = field.path.segments[1];
|
||||
@ -265,7 +264,10 @@ export const collectionTableSchema: ISchema = {
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
collectionFieldSchema,
|
||||
collectionFieldSchema: {
|
||||
type: 'void',
|
||||
'x-component': 'CollectionFields',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -202,7 +202,7 @@ export const useCollectionFilterOptions = (collectionName: string) => {
|
||||
|
||||
export const useLinkageCollectionFilterOptions = (collectionName: string) => {
|
||||
const { getCollectionFields, getInterface } = useCollectionManager();
|
||||
const fields = getCollectionFields(collectionName).filter((v)=>!['o2m', 'm2m'].includes(v.interface))
|
||||
const fields = getCollectionFields(collectionName).filter((v) => !['o2m', 'm2m'].includes(v.interface));
|
||||
const field2option = (field, depth) => {
|
||||
if (!field.interface) {
|
||||
return;
|
||||
@ -232,7 +232,7 @@ export const useLinkageCollectionFilterOptions = (collectionName: string) => {
|
||||
option['children'] = children;
|
||||
}
|
||||
if (nested) {
|
||||
const targetFields = getCollectionFields(field.target).filter((v)=>!['o2m', 'm2m'].includes(v.interface))
|
||||
const targetFields = getCollectionFields(field.target).filter((v) => !['o2m', 'm2m'].includes(v.interface));
|
||||
const options = getOptions(targetFields, depth + 1).filter(Boolean);
|
||||
option['children'] = option['children'] || [];
|
||||
option['children'].push(...options);
|
||||
@ -253,7 +253,6 @@ export const useLinkageCollectionFilterOptions = (collectionName: string) => {
|
||||
return options;
|
||||
};
|
||||
|
||||
|
||||
export const useFilterDataSource = (options) => {
|
||||
const { name } = useCollection();
|
||||
const data = useCollectionFilterOptions(name);
|
||||
@ -378,7 +377,7 @@ export const useDestroyAction = () => {
|
||||
|
||||
export const useBulkDestroyAction = () => {
|
||||
const { state, setState, refresh } = useResourceActionContext();
|
||||
const { resource, targetKey } = useResourceContext();
|
||||
const { resource } = useResourceContext();
|
||||
return {
|
||||
async run() {
|
||||
await resource.destroy({
|
||||
@ -431,8 +430,13 @@ export const useDestroyActionAndRefreshCM = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useDeleteButtonDisabled = () => {
|
||||
const { interface: i, deletable = true } = useRecord();
|
||||
export const useDeleteButtonDisabled = (record?: any) => {
|
||||
const recordFromProvider = useRecord();
|
||||
return isDeleteButtonDisabled(record || recordFromProvider);
|
||||
};
|
||||
|
||||
export const isDeleteButtonDisabled = (record?: any) => {
|
||||
const { interface: i, deletable = true } = record || {};
|
||||
|
||||
return !deletable || i === 'id';
|
||||
};
|
||||
|
@ -7,7 +7,8 @@ import { CollectionManagerContext } from '../context';
|
||||
import { CollectionFieldOptions } from '../types';
|
||||
|
||||
export const useCollectionManager = () => {
|
||||
const { refreshCM, service, interfaces, collections, templates } = useContext(CollectionManagerContext);
|
||||
const { refreshCM, updateCollection, service, interfaces, collections, templates } =
|
||||
useContext(CollectionManagerContext);
|
||||
const compile = useCompile();
|
||||
const getInheritedFields = (name) => {
|
||||
const inheritKeys = getInheritCollections(name);
|
||||
@ -241,6 +242,7 @@ export const useCollectionManager = () => {
|
||||
getInheritCollections,
|
||||
getChildrenCollections,
|
||||
refreshCM: () => refreshCM?.(),
|
||||
updateCollection,
|
||||
get(name: string) {
|
||||
return collections?.find((collection) => collection.name === name);
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ export interface CollectionManagerOptions {
|
||||
collections?: any[];
|
||||
templates?: any;
|
||||
refreshCM?: () => Promise<void>;
|
||||
updateCollection?: (collection: any) => void;
|
||||
}
|
||||
|
||||
export interface FieldOptions {
|
||||
|
@ -213,6 +213,7 @@ export default {
|
||||
"Optional fields": "Optional fields",
|
||||
"System fields": "System fields",
|
||||
"General fields": "General fields",
|
||||
"Inherited fields": "Inherited fields",
|
||||
"Parent collection fields": "Parent collection fields",
|
||||
"Basic": "Basic",
|
||||
"Single line text": "Single line text",
|
||||
|
@ -595,4 +595,14 @@ export default {
|
||||
|
||||
'Click or drag file to this area to upload': 'クリックまたはドラッグしてファイルをアップロード',
|
||||
'Support for a single or bulk upload, file size should not exceed': '単一または複数のファイルをアップロードできます。ファイルサイズは',
|
||||
'Default title for each record': '各レコードのデフォルトタイトル',
|
||||
|
||||
'If collection inherits, choose inherited collections as templates': 'コレクションが継承されている場合、継承されたコレクションをテンプレートとして選択してください',
|
||||
'Select an existing piece of data as the initialization data for the form': '既存のデータを選択して、フォームの初期化データとして使用します',
|
||||
'Only the selected fields will be used as the initialization data for the form': '選択したフィールドのみがフォームの初期化データとして使用されます',
|
||||
'Template Data': 'テンプレートデータ',
|
||||
'Data fields': 'データフィールド',
|
||||
'Add template': 'テンプレートを追加',
|
||||
'Display data template selector': 'データテンプレートセレクターを表示',
|
||||
'Form data templates': 'フォームデータテンプレート',
|
||||
}
|
||||
|
@ -173,6 +173,7 @@ export default {
|
||||
"Optional fields": "Campos opcionais",
|
||||
"System fields": "Campos do sistema",
|
||||
"General fields": "Campos gerais",
|
||||
"Inherited fields": "Campos herdados",
|
||||
"Parent collection fields": "Campos da coleção pai",
|
||||
"Basic": "Básico",
|
||||
"Single line text": "Texto de uma linha",
|
||||
@ -647,4 +648,14 @@ export default {
|
||||
|
||||
'Click or drag file to this area to upload': 'Clique ou arraste o arquivo para esta área para fazer o upload',
|
||||
'Support for a single or bulk upload, file size should not exceed': 'Suporte para upload único ou em massa, o tamanho do arquivo não deve exceder',
|
||||
'Default title for each record': 'Título padrão para cada registro',
|
||||
|
||||
'If collection inherits, choose inherited collections as templates': 'Se a coleção herda, escolha as coleções herdadas como modelos',
|
||||
'Select an existing piece of data as the initialization data for the form': 'Selecione um pedaço de dados existente como os dados de inicialização para o formulário',
|
||||
'Only the selected fields will be used as the initialization data for the form': 'Somente os campos selecionados serão usados como dados de inicialização para o formulário',
|
||||
'Template Data': 'Dados do modelo',
|
||||
'Data fields': 'Campos de dados',
|
||||
'Add template': 'Adicionar modelo',
|
||||
'Display data template selector': 'Exibir seletor de modelo de dados',
|
||||
'Form data templates': 'Modelos de dados do formulário',
|
||||
};
|
||||
|
@ -502,4 +502,14 @@ export default {
|
||||
|
||||
'Click or drag file to this area to upload': "Нажмите или перетащите файл в эту область, чтобы загрузить",
|
||||
'Support for a single or bulk upload, file size should not exceed': "Поддержка одиночной или массовой загрузки, размер файла не должен превышать",
|
||||
'Default title for each record': "Заголовок по умолчанию для каждой записи",
|
||||
|
||||
'If collection inherits, choose inherited collections as templates': "Если коллекция наследуется, выберите наследуемые коллекции в качестве шаблонов",
|
||||
'Select an existing piece of data as the initialization data for the form': "Выберите существующие данные в качестве исходных данных для формы",
|
||||
'Only the selected fields will be used as the initialization data for the form': "Только выбранные поля будут использоваться в качестве исходных данных для формы",
|
||||
'Template Data': "Шаблон данных",
|
||||
'Data fields': "Поля данных",
|
||||
'Add template': "Добавить шаблон",
|
||||
'Display data template selector': "Отображать селектор шаблона данных",
|
||||
'Form data templates': "Шаблоны данных формы",
|
||||
}
|
||||
|
@ -174,6 +174,7 @@ export default {
|
||||
"Field display name": "Alan görünen adı",
|
||||
"Field type": "Alan türü",
|
||||
"Field interface": "Alan arayüzü",
|
||||
"Title field": "Başlık alanı",
|
||||
"Date format": "Tarih formatı",
|
||||
"Year/Month/Day": "Yıl/Ay/Gün",
|
||||
"Year-Month-Day": "Yıl-Ay-Gün",
|
||||
@ -273,7 +274,6 @@ export default {
|
||||
"Allow linking to multiple records": "Birden çok kayda bağlanmaya izin ver",
|
||||
"Allow uploading multiple files": "Birden çok dosya yüklemeye izin ver",
|
||||
"Configure calendar": "Takvimi yapılandır",
|
||||
"Title field": "Başlık alanı",
|
||||
"Start date field": "Başlangıç tarihi alanı",
|
||||
"End date field": "Bitiş tarihi alanı",
|
||||
"Navigate": "Navigate",
|
||||
@ -501,4 +501,14 @@ export default {
|
||||
|
||||
'Click or drag file to this area to upload': "Dosyayı yüklemek için buraya tıklayın veya sürükleyin",
|
||||
'Support for a single or bulk upload, file size should not exceed': "Tek veya toplu yükleme destekler, dosya boyutu aşmamalıdır",
|
||||
'Default title for each record': "Her kayıt için varsayılan başlık",
|
||||
|
||||
'If collection inherits, choose inherited collections as templates': "Koleksiyon miras alırsa, kalıtılan koleksiyonları şablon olarak seçin",
|
||||
'Select an existing piece of data as the initialization data for the form': 'Formun başlangıç verileri olarak mevcut bir veri parçasını seçin',
|
||||
'Only the selected fields will be used as the initialization data for the form': 'Yalnızca seçilen alanlar, formun başlangıç verileri olarak kullanılacaktır',
|
||||
'Template Data': 'Şablon Verisi',
|
||||
'Data fields': 'Veri alanları',
|
||||
'Add template': 'Şablon ekle',
|
||||
'Display data template selector': 'Veri şablonu seçicisini görüntüle',
|
||||
'Form data templates': 'Form veri şablonları',
|
||||
}
|
||||
|
@ -227,6 +227,7 @@ export default {
|
||||
"Optional fields": "可选字段",
|
||||
"System fields": "系统字段",
|
||||
"General fields": "普通字段",
|
||||
"Inherited fields": "继承字段",
|
||||
"Parent collection fields": "父表字段",
|
||||
"Basic": "基本类型",
|
||||
"Single line text": "单行文本",
|
||||
@ -743,4 +744,14 @@ export default {
|
||||
|
||||
'Click or drag file to this area to upload': '点击或拖拽文件到此区域上传',
|
||||
'Support for a single or bulk upload, file size should not exceed': '支持单个或批量上传,文件大小不能超过',
|
||||
'Default title for each record': '用作数据的默认标题',
|
||||
|
||||
'If collection inherits, choose inherited collections as templates': '当前表有继承关系时,可选择继承链路上的表作为模板来源',
|
||||
'Select an existing piece of data as the initialization data for the form': '选择一条已有的数据作为表单的初始化数据',
|
||||
'Only the selected fields will be used as the initialization data for the form': '仅选择的字段才会作为表单的初始化数据',
|
||||
'Template Data': '模板数据',
|
||||
'Data fields': '数据字段',
|
||||
'Add template': '添加模板',
|
||||
'Display data template selector': '显示数据模板选择框',
|
||||
'Form data templates': '表单数据模板',
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ export const Action: ComposedAction = observer((props: any) => {
|
||||
const values = useRecord();
|
||||
const designerProps = fieldSchema['x-designer-props'];
|
||||
const openMode = fieldSchema?.['x-component-props']?.['openMode'];
|
||||
const disabled = form.disabled || field.disabled;
|
||||
const disabled = form.disabled || field.disabled || props.disabled;
|
||||
const openSize = fieldSchema?.['x-component-props']?.['openSize'];
|
||||
const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
|
||||
const { designable } = useDesignable();
|
||||
|
@ -2,16 +2,16 @@ import { MenuOutlined } from '@ant-design/icons';
|
||||
import { css } from '@emotion/css';
|
||||
import { ArrayField, Field } from '@formily/core';
|
||||
import {
|
||||
observer,
|
||||
RecursionField,
|
||||
Schema,
|
||||
SchemaExpressionScopeContext,
|
||||
observer,
|
||||
useField,
|
||||
useFieldSchema,
|
||||
SchemaExpressionScopeContext,
|
||||
} from '@formily/react';
|
||||
import { Table, TableColumnProps } from 'antd';
|
||||
import { default as classNames, default as cls } from 'classnames';
|
||||
import React, { useState, useContext } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import ReactDragListView from 'react-drag-listview';
|
||||
import { DndContext } from '../..';
|
||||
import { RecordIndexProvider, RecordProvider, useRequest, useSchemaInitializer } from '../../../';
|
||||
@ -35,7 +35,7 @@ const useTableColumns = () => {
|
||||
if (isColumnComponent(s) && useScope(s['x-visible'])) {
|
||||
return buf.concat([s]);
|
||||
}
|
||||
return buf
|
||||
return buf;
|
||||
}, [])
|
||||
.map((s: Schema) => {
|
||||
return {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { connect, mapProps, observer } from '@formily/react';
|
||||
import { Tree as AntdTree } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCollectionManager } from '../../collection-manager';
|
||||
import { AssociationSelect, SchemaComponent } from '../../schema-component';
|
||||
import { AsDefaultTemplate } from './components/AsDefaultTemplate';
|
||||
import { ArrayCollapse } from './components/DataTemplateTitle';
|
||||
@ -19,6 +21,9 @@ export const FormDataTemplates = observer((props: any) => {
|
||||
const { useProps } = props;
|
||||
const { defaultValues, collectionName } = useProps();
|
||||
const { collectionList, getEnableFieldTree } = useCollectionState(collectionName);
|
||||
const { getCollection } = useCollectionManager();
|
||||
const collection = getCollection(collectionName);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SchemaComponent
|
||||
@ -54,7 +59,7 @@ export const FormDataTemplates = observer((props: any) => {
|
||||
type: 'string',
|
||||
title: '{{ t("Collection") }}',
|
||||
required: true,
|
||||
description: '当前表有继承关系时,可选择继承链路上的表作为模板来源',
|
||||
description: t('If collection inherits, choose inherited collections as templates'),
|
||||
default: collectionName,
|
||||
'x-display': collectionList.length > 1 ? 'visible' : 'hidden',
|
||||
'x-decorator': 'FormItem',
|
||||
@ -67,7 +72,7 @@ export const FormDataTemplates = observer((props: any) => {
|
||||
type: 'number',
|
||||
title: '{{ t("Template Data") }}',
|
||||
required: true,
|
||||
description: '选择一条已有的数据作为表单的初始化数据',
|
||||
description: t('Select an existing piece of data as the initialization data for the form'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': AssociationSelect,
|
||||
'x-component-props': {
|
||||
@ -79,14 +84,14 @@ export const FormDataTemplates = observer((props: any) => {
|
||||
objectValue: false,
|
||||
manual: false,
|
||||
mapOptions: (item) => {
|
||||
// TODO: 应该使用 item.title 字段的值作为 label
|
||||
return {
|
||||
label: item.id,
|
||||
...item,
|
||||
[collection.titleField || 'label']: `#${item.id} ${item[collection.titleField] || ''}`,
|
||||
value: item.id,
|
||||
};
|
||||
},
|
||||
fieldNames: {
|
||||
label: 'label',
|
||||
label: collection.titleField || 'label',
|
||||
value: 'value',
|
||||
},
|
||||
},
|
||||
@ -110,7 +115,7 @@ export const FormDataTemplates = observer((props: any) => {
|
||||
type: 'array',
|
||||
title: '{{ t("Data fields") }}',
|
||||
required: true,
|
||||
description: '仅选择的字段才会作为表单的初始化数据',
|
||||
description: t('Only the selected fields will be used as the initialization data for the form'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': Tree,
|
||||
'x-component-props': {
|
||||
|
@ -164,6 +164,7 @@
|
||||
"Optional fields": "Optional fields",
|
||||
"System fields": "System fields",
|
||||
"General fields": "General fields",
|
||||
"Inherited fields": "Inherited fields",
|
||||
"Parent collection fields": "Parent collection fields",
|
||||
"Basic": "Basic",
|
||||
"Single line text": "Single line text",
|
||||
|
@ -149,6 +149,7 @@
|
||||
"Optional fields": "Campos opcionais",
|
||||
"System fields": "Campos do sistema",
|
||||
"General fields": "Campos gerais",
|
||||
"Inherited fields": "Campos herdados",
|
||||
"Parent collection fields": "Campos da coleção pai",
|
||||
"Basic": "Básico",
|
||||
"Single line text": "Texto de linha única",
|
||||
|
@ -164,6 +164,7 @@
|
||||
"Optional fields": "可选字段",
|
||||
"System fields": "系统字段",
|
||||
"General fields": "普通字段",
|
||||
"Inherited fields": "继承字段",
|
||||
"Parent collection fields": "父表字段",
|
||||
"Basic": "基本类型",
|
||||
"Single line text": "单行文本",
|
||||
|
Loading…
Reference in New Issue
Block a user