mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 08:36:44 +00:00
feat(data-source-manager): filterTargetKey configuration optimization (#4766)
* feat(data-source-manager): filterTargetKey configuration optimization * fix: bugs * fix: improve code * fix: error * fix: error
This commit is contained in:
parent
7459da6aab
commit
a86315de61
@ -85,9 +85,9 @@ export class SqlCollectionTemplate extends CollectionTemplate {
|
||||
},
|
||||
...getConfigurableProperties('category'),
|
||||
filterTargetKey: {
|
||||
title: `{{ t("Filter target key")}}`,
|
||||
title: `{{ t("Record unique key")}}`,
|
||||
type: 'single',
|
||||
description: `{{t( "Filter data based on the specific field, with the requirement that the field value must be unique.")}}`,
|
||||
description: `{{t( "If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.")}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': ['{{useAsyncDataSource(loadFilterTargetKeys)}}'],
|
||||
|
@ -148,9 +148,9 @@ export class ViewCollectionTemplate extends CollectionTemplate {
|
||||
},
|
||||
},
|
||||
filterTargetKey: {
|
||||
title: `{{ t("Filter target key")}}`,
|
||||
title: `{{ t("Record unique key")}}`,
|
||||
type: 'single',
|
||||
description: `{{t( "Filter data based on the specific field, with the requirement that the field value must be unique.")}}`,
|
||||
description: `{{t( "If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.")}}`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': ['{{useAsyncDataSource(loadFilterTargetKeys)}}'],
|
||||
|
@ -225,9 +225,15 @@ export class Collection {
|
||||
return this.options.resource;
|
||||
}
|
||||
|
||||
setOption(key: string, value: any) {
|
||||
this.options[key] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
getOption<K extends keyof CollectionOptions>(key: K): CollectionOptions[K] {
|
||||
return this.options[key];
|
||||
}
|
||||
|
@ -932,7 +932,9 @@
|
||||
"Separator": "分隔符",
|
||||
"Prefix": "前缀",
|
||||
"Suffix": "后缀",
|
||||
"Record unique key": "记录唯一标识符",
|
||||
"Filter target key": "筛选目标键",
|
||||
"If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.": "当数据表没有主键时,你需要配置记录唯一标识符,用于在区块中定位行记录,不配置将无法创建该表的数据区块。",
|
||||
"Filter data based on the specific field, with the requirement that the field value must be unique.": "根据特定的字段筛选数据,字段值必须具备唯一性。",
|
||||
"Multiply by": "乘以",
|
||||
"Divide by": "除以",
|
||||
|
@ -100,7 +100,8 @@ test.describe('create collection', () => {
|
||||
await collectionSettings.change('Store the last update time of each record', false);
|
||||
await collectionSettings.submit();
|
||||
|
||||
await expect(page.getByRole('cell', { name: collectionDisplayName, exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: collectionDisplayName })).toBeVisible();
|
||||
await expect(page.getByRole('cell', { name: collectionName, exact: true })).toBeVisible();
|
||||
|
||||
await collectionManagerPage.deleteItem(collectionName);
|
||||
});
|
||||
|
@ -8,34 +8,35 @@
|
||||
*/
|
||||
|
||||
import { createForm, Field } from '@formily/core';
|
||||
import { FieldContext, FormContext, useField, RecursionField } from '@formily/react';
|
||||
import { message } from 'antd';
|
||||
import React, { useContext, useMemo, useEffect, createContext, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { FieldContext, FormContext, RecursionField, useField } from '@formily/react';
|
||||
import {
|
||||
useAPIClient,
|
||||
SchemaComponentOptions,
|
||||
useRecord,
|
||||
useAttach,
|
||||
Collection,
|
||||
ResourceActionContext,
|
||||
ResourceActionProvider,
|
||||
useResourceContext,
|
||||
useCompile,
|
||||
SchemaComponentOptions,
|
||||
Select,
|
||||
Collection,
|
||||
useAPIClient,
|
||||
useAttach,
|
||||
useCompile,
|
||||
useDataSourceManager,
|
||||
useRecord,
|
||||
useResourceContext,
|
||||
} from '@nocobase/client';
|
||||
import { collection, fieldsTableSchema } from './schema/collectionFields';
|
||||
import { TitleField } from './components/TitleField';
|
||||
import { CollectionFieldInterfaceSelect } from './components/CollectionFieldInterfaceSelect';
|
||||
import { message } from 'antd';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { AddCollectionField } from './AddFieldAction';
|
||||
import { ForeignKey, SourceCollection, SourceKey, TargetKey, ThroughCollection } from './components';
|
||||
import { CollectionFieldInterfaceSelect } from './components/CollectionFieldInterfaceSelect';
|
||||
import { FieldTitleInput } from './components/FieldTitleInput';
|
||||
import { useBulkDestroyActionAndRefreshCM, useDestroyActionAndRefreshCM } from './hooks';
|
||||
import { EditCollectionField } from './EditFieldAction';
|
||||
import { SourceCollection, TargetKey, SourceKey, ForeignKey, ThroughCollection } from './components';
|
||||
import { FieldType } from './components/FieldType';
|
||||
import { TitleField } from './components/TitleField';
|
||||
import { UnSupportFields } from './components/UnSupportFields';
|
||||
import { EditCollectionField } from './EditFieldAction';
|
||||
import { FilterTargetKeyAlert } from './FilterTargetKeyAlert';
|
||||
import { useBulkDestroyActionAndRefreshCM, useDestroyActionAndRefreshCM } from './hooks';
|
||||
import { collection, fieldsTableSchema } from './schema/collectionFields';
|
||||
|
||||
const RemoteCollectionContext = createContext<{
|
||||
targetCollection: Collection;
|
||||
@ -186,6 +187,7 @@ export const CollectionFields = () => {
|
||||
targetCollection,
|
||||
}}
|
||||
>
|
||||
<FilterTargetKeyAlert collectionName={name} />
|
||||
<ResourceActionProvider {...resourceActionProps}>
|
||||
<FormContext.Provider value={form}>
|
||||
<FieldContext.Provider value={f}>
|
||||
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ExclamationCircleTwoTone } from '@ant-design/icons';
|
||||
import { useCollectionRecordData, useCompile } from '@nocobase/client';
|
||||
import { Popover } from 'antd';
|
||||
import React from 'react';
|
||||
import { SetFilterTargetKey } from './SetFilterTargetKey';
|
||||
|
||||
export const CollectionTitle = () => {
|
||||
const record = useCollectionRecordData();
|
||||
const compile = useCompile();
|
||||
if (record.filterTargetKey) {
|
||||
return compile(record.title);
|
||||
}
|
||||
return (
|
||||
<div style={{ display: 'inline' }}>
|
||||
<Popover trigger={['click']} content={<SetFilterTargetKey size={'small'} style={{ width: '20em' }} />}>
|
||||
<ExclamationCircleTwoTone style={{ marginRight: '0.3em' }} twoToneColor="#faad14" />
|
||||
</Popover>
|
||||
{compile(record.title)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -10,7 +10,6 @@
|
||||
import { ArrayTable } from '@formily/antd-v5';
|
||||
import { ISchema, useForm } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import {
|
||||
ActionContextProvider,
|
||||
IField,
|
||||
@ -18,15 +17,16 @@ import {
|
||||
SchemaComponent,
|
||||
useAPIClient,
|
||||
useActionContext,
|
||||
useCancelAction,
|
||||
useCollectionManager_deprecated,
|
||||
useCompile,
|
||||
useDataSourceManager,
|
||||
useRecord,
|
||||
useRequest,
|
||||
useResourceActionContext,
|
||||
useResourceContext,
|
||||
useCancelAction,
|
||||
useDataSourceManager,
|
||||
} from '@nocobase/client';
|
||||
import { tval } from '@nocobase/utils/client';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import omit from 'lodash/omit';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@ -69,10 +69,10 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema
|
||||
properties: {
|
||||
...omit(properties, 'category', 'inherits', 'moreOptions'),
|
||||
filterTargetKey: {
|
||||
title: `{{ t("Filter target key",{ ns: "${NAMESPACE}" }) }}`,
|
||||
title: `{{ t("Record unique key") }}`,
|
||||
type: 'single',
|
||||
description: tval(
|
||||
'Filter data based on the specific field, with the requirement that the field value must be unique.',
|
||||
'If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.',
|
||||
{ ns: NAMESPACE },
|
||||
),
|
||||
'x-decorator': 'FormItem',
|
||||
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useApp } from '@nocobase/client';
|
||||
import { Alert } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { SetFilterTargetKey } from './SetFilterTargetKey';
|
||||
|
||||
export const FilterTargetKeyAlert = ({ collectionName }) => {
|
||||
const app = useApp();
|
||||
const { name: dataSourceKey = 'main' } = useParams();
|
||||
const collection = useMemo(() => {
|
||||
const cm = app.getCollectionManager(dataSourceKey);
|
||||
return cm.getCollection(collectionName);
|
||||
}, [app, dataSourceKey, collectionName]);
|
||||
return (
|
||||
!collection?.filterTargetKey && (
|
||||
<Alert style={{ marginBottom: 16 }} type="warning" message={<SetFilterTargetKey />} />
|
||||
)
|
||||
);
|
||||
};
|
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useAPIClient, useApp, useCollectionRecordData, useCompile, useResourceActionContext } from '@nocobase/client';
|
||||
import { Button, Popconfirm, Select, Space } from 'antd';
|
||||
import React, { useContext, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useDSMTranslation } from '../../locale';
|
||||
import { CollectionListContext } from '../MainDataSourceManager/Configuration/CollectionFields';
|
||||
|
||||
export const SetFilterTargetKey = (props) => {
|
||||
const { size, style } = props;
|
||||
const api = useAPIClient();
|
||||
const { name: dataSourceKey = 'main' } = useParams();
|
||||
const record = useCollectionRecordData();
|
||||
const app = useApp();
|
||||
const [filterTargetKey, setFilterTargetKey] = useState();
|
||||
const [title, setTitle] = useState();
|
||||
const compile = useCompile();
|
||||
const collection = useMemo(() => {
|
||||
const cm = app.getCollectionManager(dataSourceKey);
|
||||
return cm.getCollection(record.name);
|
||||
}, [app, dataSourceKey, record.name]);
|
||||
const options = useMemo(() => {
|
||||
const cm = app.getCollectionManager(dataSourceKey);
|
||||
const fields = cm.getCollectionFields(record.name);
|
||||
return fields
|
||||
.filter((field) => {
|
||||
if (!field.interface) {
|
||||
return false;
|
||||
}
|
||||
const interfaceOptions = app.dataSourceManager.collectionFieldInterfaceManager.getFieldInterface(
|
||||
field.interface,
|
||||
);
|
||||
if (interfaceOptions.titleUsable) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map((field) => ({
|
||||
label: compile(field.uiSchema?.title),
|
||||
value: field.name,
|
||||
}));
|
||||
}, [app, compile, dataSourceKey, record.name]);
|
||||
const { refresh } = useResourceActionContext();
|
||||
const ctx = useContext(CollectionListContext);
|
||||
const { t } = useDSMTranslation();
|
||||
|
||||
return (
|
||||
<div style={{ ...style }}>
|
||||
{/* 当数据表没有主键(Primary key)时,你需要配置记录唯一标识符(Record unique
|
||||
key),用于在区块中定位行记录,不配置将无法创建该表的数据区块。 */}
|
||||
{t(
|
||||
`If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.`,
|
||||
)}
|
||||
{size === 'small' ? <br /> : ' '}
|
||||
<Space.Compact style={{ marginTop: 5 }}>
|
||||
<Select
|
||||
onChange={(value, option) => {
|
||||
setFilterTargetKey(value);
|
||||
setTitle(option['label']);
|
||||
}}
|
||||
placeholder={t('Select field')}
|
||||
size={'small'}
|
||||
options={options}
|
||||
/>
|
||||
<Popconfirm
|
||||
placement="bottom"
|
||||
title={
|
||||
<div style={{ width: '15em' }}>
|
||||
{title
|
||||
? t(
|
||||
'Are you sure you want to set the "{{title}}" field as a record unique key? This setting cannot be changed after it\'s been set.',
|
||||
{ title },
|
||||
)
|
||||
: t('Please select a field.')}
|
||||
</div>
|
||||
}
|
||||
onConfirm={async () => {
|
||||
if (!filterTargetKey) {
|
||||
return;
|
||||
}
|
||||
if (dataSourceKey === 'main') {
|
||||
await api.request({
|
||||
url: `collections:update?filterByTk=${record.name}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
filterTargetKey,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await api.request({
|
||||
url: `dataSources/${dataSourceKey}/collections:update?filterByTk=${record.name}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
filterTargetKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
ctx?.refresh?.();
|
||||
refresh();
|
||||
// await app.dataSourceManager.getDataSource(dataSourceKey).reload();
|
||||
collection.setOption('filterTargetKey', filterTargetKey);
|
||||
}}
|
||||
>
|
||||
<Button type={'primary'} size={'small'}>
|
||||
{t('OK')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -9,9 +9,10 @@
|
||||
|
||||
import { ISchema, Schema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { CollectionOptions, i18n, useAPIClient } from '@nocobase/client';
|
||||
import { message } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient, i18n, CollectionOptions } from '@nocobase/client';
|
||||
import { CollectionTitle } from '../CollectionTitle';
|
||||
|
||||
export const compile = (source) => {
|
||||
return Schema.compile(source, { t: i18n.t });
|
||||
@ -30,7 +31,7 @@ export const collection: CollectionOptions = {
|
||||
uiSchema: {
|
||||
title: '{{ t("Collection display name") }}',
|
||||
type: 'number',
|
||||
'x-component': 'Input',
|
||||
'x-component': CollectionTitle,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
@ -10,9 +10,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { createForm, Field } from '@formily/core';
|
||||
import { FieldContext, FormContext, useField } from '@formily/react';
|
||||
import { Space, Switch, Table, TableColumnProps, Tag, Tooltip, message } from 'antd';
|
||||
import React, { createContext, useContext, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Action,
|
||||
AddCollectionField,
|
||||
@ -26,6 +23,7 @@ import {
|
||||
SchemaComponent,
|
||||
SyncFieldsAction,
|
||||
SyncSQLFieldsAction,
|
||||
useAPIClient,
|
||||
useAttach,
|
||||
useBulkDestroyActionAndRefreshCM,
|
||||
useCollectionManager_deprecated,
|
||||
@ -36,9 +34,12 @@ import {
|
||||
useResourceActionContext,
|
||||
useResourceContext,
|
||||
ViewCollectionField,
|
||||
useAPIClient,
|
||||
} from '@nocobase/client';
|
||||
import { message, Space, Switch, Table, TableColumnProps, Tag, Tooltip } from 'antd';
|
||||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FilterTargetKeyAlert } from '../../CollectionsManager/FilterTargetKeyAlert';
|
||||
import { collection } from './schemas/collectionFields';
|
||||
const resourceActionProps = {
|
||||
association: {
|
||||
@ -59,7 +60,7 @@ const resourceActionProps = {
|
||||
},
|
||||
};
|
||||
|
||||
const CollectionListContext = createContext(null);
|
||||
export const CollectionListContext = createContext(null);
|
||||
|
||||
const CollectionFieldsProvider = (props) => {
|
||||
return (
|
||||
@ -439,6 +440,7 @@ const CollectionFieldsInternal = () => {
|
||||
return (
|
||||
<FormContext.Provider value={form}>
|
||||
<FieldContext.Provider value={f}>
|
||||
<FilterTargetKeyAlert collectionName={name} />
|
||||
<Space
|
||||
align={'end'}
|
||||
className={css`
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
import { ISchema, Schema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import type { CollectionOptions } from '@nocobase/client';
|
||||
import { CollectionCategory, CollectionTemplateTag, i18n, useAPIClient } from '@nocobase/client';
|
||||
import { message } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAPIClient, i18n } from '@nocobase/client';
|
||||
import { CollectionCategory, CollectionTemplateTag } from '@nocobase/client';
|
||||
import type { CollectionOptions } from '@nocobase/client';
|
||||
import { CollectionTitle } from '../../../CollectionsManager/CollectionTitle';
|
||||
|
||||
const compile = (source) => {
|
||||
return Schema.compile(source, { t: i18n.t });
|
||||
@ -198,11 +198,12 @@ export const collectionTableSchema: ISchema = {
|
||||
properties: {
|
||||
column1: {
|
||||
type: 'void',
|
||||
title: '{{t("Collection display name")}}',
|
||||
'x-decorator': 'Table.Column.Decorator',
|
||||
'x-component': 'Table.Column',
|
||||
properties: {
|
||||
title: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-component': CollectionTitle,
|
||||
'x-read-pretty': true,
|
||||
},
|
||||
},
|
||||
|
@ -8,9 +8,14 @@
|
||||
*/
|
||||
|
||||
import { i18n } from '@nocobase/client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const NAMESPACE = 'data-source-manager';
|
||||
|
||||
export function lang(key: string, options = {}) {
|
||||
return i18n.t(key, { ...options, ns: NAMESPACE });
|
||||
}
|
||||
|
||||
export function useDSMTranslation() {
|
||||
return useTranslation([NAMESPACE, 'client'], { nsMode: 'fallback' });
|
||||
}
|
@ -31,5 +31,10 @@
|
||||
"Data source synchronization in progress": "数据源同步中",
|
||||
"Data source synchronization successful": "数据源同步成功",
|
||||
"Filter target key":"筛选目标键",
|
||||
"Select field": "选择字段",
|
||||
"OK": "确定",
|
||||
"Please select a field.": "请选择一个字段。",
|
||||
"Are you sure you want to set the \"{{title}}\" field as a record unique key? This setting cannot be changed after it's been set.": "你确定将 “{{title}}” 字段设置为主键吗?设置成功后不可修改。",
|
||||
"If a collection lacks a primary key, you must configure a unique record key to locate row records within a block, failure to configure this will prevent the creation of data blocks for the collection.": "当数据表没有主键时,你需要配置记录唯一标识符,用于在区块中定位行记录,不配置将无法创建该表的数据区块。",
|
||||
"Filter data based on the specific field, with the requirement that the field value must be unique.": "根据特定的字段筛选数据,字段值必须具备唯一性。"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user