mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 01:36:52 +00:00
feat: improve collection manager (#1013)
* feat: 图形化管理数据表 * feat: 图形化管理数据表 * feat: 图形化管理数据表 * feat: 图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 完善图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat: 样式优化图形化管理数据表 * feat(collection-manager): add foreignKey Field and support relate field record foreignKey info through collection record into collections and foreignKey field record info fields * fix(collection-manager): if has through collection then don't create through collections record * fix(client/route-switch): skip sub routes * feat: 添加graphpostion * feat: 图形化collection新增表时刷新数据 * fix(collection-manager): refactor afterCreateForRelateField * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化collection存储位置 * feat: 图形化样式优化 * feat: styling * feat: 图形化样式优化 * feat: 图形化样式优化 * feat: 图形化数据表多语言完善 * feat: 图形化数据表多语言完善 * feat: improve code * feat: 图形化数据表连线样式修改 * feat: 图形化数据表样式修改 * feat: 图形化数据表样式修改 * feat: 图形化数据表样式修改 * feat: 图形化数据表样式修改 * fix(collection-manager): fix afterCreateForRelateField * feat: 样式优化 * feat: 样式优化 * feat: afterCreateForForeignKeyField * fix: timestamps: false * feat: 连线锚点优化 * fix(collection-manager): when del foreign key field, relate fields will be del too * fix: update package.json * fix: update package.json * feat: 文件名大小写 * feat: 连线锚点优化 * feat: 连线锚点通过计算得到样式优化 * feat: 连线锚点通过计算得到样式优化 * fix: fk * fix: remove index * feat: 连线hover时高亮 * fix: test error * feat: 初始化计算位置 * feat: 初始化时计算坐标位置 * feat: 初始化时计算坐标位置 * feat: improve code (#933) * fix: built in * feat: 没有关系字段时也要连线 * feat: 自关联也要连线 * fix: styling * feat: 滚动条问题 * feat: 拖拽优化 * feat: 画布paddig优化 * feat: 编辑时支持反向关联字段配置 * feat: 画布拖拽滚动优化 * feat: 画布拖拽滚动优化 * fix: reload * feat: 修复数据表新建重叠 * fix: refreshCM & refreshGM * feat: 修复表达式输入框显示异常 * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * fix: 消除代码警告 * fix: 消除代码警告 * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化(增量渲染) * feat: 渲染性能优化 * feat: 渲染性能优化 * feat: 外键生成在位置在前面 * feat: 限制表最多显示10个字段其余滚动 * feat: 移动表位置的连线重新计算最优位置 * fix: error * feat: 布局自动换行 * fix: test error * fix: xpipe.eq * fix: upgrade error * fix: upgrade error * feat: 选中表时只显示和目标表关联的表和连线 * feat: 连线优化 * fix: maxListenersExceededWarning * feat: 连线优化 * feat: powerby样式优化 * feat: 表筛选优化 * feat: 新建字段优化 * feat: 点击线高亮主外键和关联字段 * feat: 点击线高亮主外键和关联字段 * feat: 鼠标hover连线高亮主外键和关联字段 * fix(collection-manager): foreign key sorting should follow ID * fix(client/config-relation-field): set Relation field's ReverseField default value is false * feat: 卡片默认显示主外键和关联字段其余通过折叠展示且分组区分显示 * fix(client/collection-manager): don't display auto create through collections and foreign key only display in graph menu * feat: 样式优化 * feat: 添加字段时默认展开折叠 * feat: 样式优化 * feat: foreign field migration (#1001) * feat: 补充多语言 * feat: settings center tabs * feat: 主键判断primaryKey * fix(collection-manager): foreign key sorting should follow primaryKey * fix(client/block-select-collection): filter auto create through collections * fix(client/block-config-fields): filter isForeignKey fields * fix(client/configuration-table): relation fileds select collection filter auto create through * feat: 多对多连线高亮时全亮 * feat: 选中多对多中的一张表另一张表也显示 * feat: 连线mouseleave事件 * feat: 多语言更新 * feat: 计算新建表位置优化 * feat: 添加自动布局 * feat(client/configure-fields): categorize fields * fix(client/configure-fields): display foreign key fields * fix(client): package reference * fix: remove graph * fix: remove Co-authored-by: 唐小爱 <tangxiaoai@192.168.0.103> Co-authored-by: lyf-coder <lyf-coder@foxmail.com> Co-authored-by: katherinehhh <katherine_15995@163.com> Co-authored-by: ChengLei Shao <chareice@live.com> Co-authored-by: mytharcher <mytharcher@gmail.com>
This commit is contained in:
parent
8f5a93bf63
commit
0e70e3848a
32
.vscode/launch.json
vendored
32
.vscode/launch.json
vendored
@ -4,41 +4,13 @@
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Server",
|
||||
"cmd": "${workspaceRoot}",
|
||||
"runtimeArgs": [
|
||||
"-r", "dotenv/config",
|
||||
"-r", "tsconfig-paths/register",
|
||||
"-r", "ts-node/register"
|
||||
],
|
||||
"args": ["${workspaceRoot}/packages/app/server/src/index.ts", "start"],
|
||||
"port": 9229,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Jest Tests",
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"--inspect-brk",
|
||||
"test",
|
||||
"--runInBand",
|
||||
// could be any single file path to debug
|
||||
"${workspaceFolder}/packages/plugins/workflow/src/__tests__/instructions/parallel.test.ts"
|
||||
],
|
||||
"port": 9229,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"runtimeArgs": ["run", "--inspect-brk", "test", "--runInBand", "${fileBasenameNoExtension}"],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ export const AddCollectionField = (props) => {
|
||||
};
|
||||
|
||||
export const AddFieldAction = (props) => {
|
||||
const { scope, getContainer, item: record, children } = props;
|
||||
const { scope, getContainer, item: record, children,trigger } = props;
|
||||
const { getInterface } = useCollectionManager();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [schema, setSchema] = useState({});
|
||||
@ -154,6 +154,7 @@ export const AddFieldAction = (props) => {
|
||||
<ActionContext.Provider value={{ visible, setVisible }}>
|
||||
<Dropdown
|
||||
getPopupContainer={getContainer}
|
||||
trigger={trigger}
|
||||
overlay={
|
||||
<Menu
|
||||
style={{
|
||||
|
@ -0,0 +1,66 @@
|
||||
import { createForm, Field } from '@formily/core';
|
||||
import { FieldContext, FormContext, observer, useField, useFieldSchema } from '@formily/react';
|
||||
import { Options, Result } from 'ahooks/lib/useRequest/src/types';
|
||||
import React, { useMemo } from 'react';
|
||||
import { AsyncDataProvider, useAsyncData, useRequest } from '../..';
|
||||
import { useAttach } from '@formily/react/lib/hooks/useAttach';
|
||||
import { TableProps } from 'antd';
|
||||
import { CollectionFieldsTableArray } from './CollectionFieldsTableArray';
|
||||
|
||||
type TableVoidProps = TableProps<any> & {
|
||||
request?: any;
|
||||
useSelectedRowKeys?: any;
|
||||
useDataSource?: (
|
||||
options?: Options<any, any> & { uid?: string },
|
||||
props?: any,
|
||||
) => Result<any, any> & { state?: any; setState?: any };
|
||||
};
|
||||
|
||||
const useDefSelectedRowKeys = () => {
|
||||
const result = useAsyncData();
|
||||
return [result?.state?.selectedRowKeys, (selectedRowKeys) => result?.setState?.({ selectedRowKeys })];
|
||||
};
|
||||
const useDef = (options, props) => {
|
||||
const { request, dataSource } = props;
|
||||
if (request) {
|
||||
return useRequest(request(props), options);
|
||||
} else {
|
||||
return Promise.resolve({
|
||||
data: dataSource,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const CollectionFieldsTable: React.FC<TableVoidProps> = observer((props) => {
|
||||
const { rowKey = 'id', useDataSource = useDef, useSelectedRowKeys = useDefSelectedRowKeys } = props;
|
||||
const field = useField<Field>();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const form = useMemo(() => createForm(), []);
|
||||
const f = useAttach(form.createArrayField({ ...field.props, basePath: '' }));
|
||||
const result = useDataSource(
|
||||
{
|
||||
uid: fieldSchema['x-uid'],
|
||||
onSuccess(data) {
|
||||
form.setValues({
|
||||
[fieldSchema.name]: data?.data,
|
||||
});
|
||||
},
|
||||
},
|
||||
props,
|
||||
);
|
||||
return (
|
||||
<AsyncDataProvider value={result}>
|
||||
<FormContext.Provider value={form}>
|
||||
<FieldContext.Provider value={f}>
|
||||
<CollectionFieldsTableArray
|
||||
{...props}
|
||||
rowKey={rowKey}
|
||||
loading={result?.['loading']}
|
||||
useSelectedRowKeys={useSelectedRowKeys}
|
||||
pagination={false}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
</FormContext.Provider>
|
||||
</AsyncDataProvider>
|
||||
);
|
||||
});
|
@ -0,0 +1,220 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { ArrayField, Field } from '@formily/core';
|
||||
import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
|
||||
import { Table, TableColumnProps } from 'antd';
|
||||
import { default as classNames } from 'classnames';
|
||||
import React, { useState } from 'react';
|
||||
import { RecordIndexProvider, RecordProvider, useCollectionManager, useRequest, useSchemaInitializer } from '../..';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const isColumnComponent = (schema: Schema) => {
|
||||
return schema['x-component']?.endsWith('.Column') > -1;
|
||||
};
|
||||
|
||||
const useTableColumns = () => {
|
||||
const field = useField<ArrayField>();
|
||||
const schema = useFieldSchema();
|
||||
const { exists, render } = useSchemaInitializer(schema['x-initializer']);
|
||||
const columns = schema
|
||||
.reduceProperties((buf, s) => {
|
||||
if (isColumnComponent(s)) {
|
||||
return buf.concat([s]);
|
||||
}
|
||||
}, [])
|
||||
.map((s: Schema) => {
|
||||
return {
|
||||
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
|
||||
dataIndex: s.name,
|
||||
key: s.name,
|
||||
render: (v, record) => {
|
||||
const index = field.value?.indexOf(record);
|
||||
// console.log((Date.now() - start) / 1000);
|
||||
return (
|
||||
<RecordIndexProvider index={index}>
|
||||
<RecordProvider record={record}>
|
||||
<RecursionField schema={s} name={index} onlyRenderProperties />
|
||||
</RecordProvider>
|
||||
</RecordIndexProvider>
|
||||
);
|
||||
},
|
||||
} as TableColumnProps<any>;
|
||||
});
|
||||
if (!exists) {
|
||||
return columns;
|
||||
}
|
||||
return columns.concat({
|
||||
title: render(),
|
||||
dataIndex: 'TABLE_COLUMN_INITIALIZER',
|
||||
key: 'TABLE_COLUMN_INITIALIZER',
|
||||
});
|
||||
};
|
||||
|
||||
export const components = {
|
||||
body: {
|
||||
row: (props) => {
|
||||
return <tr {...props} />;
|
||||
},
|
||||
cell: (props) => (
|
||||
<td
|
||||
{...props}
|
||||
className={classNames(
|
||||
props.className,
|
||||
css`
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const useDef = () => {
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
||||
return [selectedRowKeys, setSelectedRowKeys];
|
||||
};
|
||||
|
||||
const useDefDataSource = (options, props) => {
|
||||
const field = useField<Field>();
|
||||
return useRequest(() => {
|
||||
return Promise.resolve({
|
||||
data: field.value,
|
||||
});
|
||||
}, options);
|
||||
};
|
||||
|
||||
const groupColumns = [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
];
|
||||
|
||||
type CategorizeKey = 'primaryAndForeignKey' | 'relation' | 'systemInfo' | 'basic';
|
||||
const sortKeyArr: Array<CategorizeKey> = ['primaryAndForeignKey', 'relation', 'basic', 'systemInfo'];
|
||||
const CategorizeKeyNameMap = new Map<CategorizeKey, string>([
|
||||
['primaryAndForeignKey', 'Primary key & Foreign key fields'],
|
||||
['relation', 'Relation fields'],
|
||||
['systemInfo', 'System fields'],
|
||||
['basic', 'General fields'],
|
||||
]);
|
||||
|
||||
interface CategorizeDataItem {
|
||||
key: CategorizeKey;
|
||||
name: string;
|
||||
data: Array<any>;
|
||||
}
|
||||
|
||||
export const CollectionFieldsTableArray: React.FC<any> = observer((props) => {
|
||||
const field = useField<ArrayField>();
|
||||
const columns = useTableColumns();
|
||||
const { t } = useTranslation();
|
||||
const { getInterface } = useCollectionManager();
|
||||
const {
|
||||
showIndex = true,
|
||||
useSelectedRowKeys = useDef,
|
||||
useDataSource = useDefDataSource,
|
||||
onChange,
|
||||
...others
|
||||
} = props;
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useSelectedRowKeys();
|
||||
|
||||
const [categorizeData, setCategorizeData] = useState<Array<CategorizeDataItem>>([]);
|
||||
useDataSource({
|
||||
onSuccess(data) {
|
||||
field.value = data?.data || [];
|
||||
// categorize field
|
||||
const categorizeMap = new Map<CategorizeKey, any>();
|
||||
const addCategorizeVal = (categorizeKey: CategorizeKey, val) => {
|
||||
let fieldArr = categorizeMap.get(categorizeKey);
|
||||
if (!fieldArr) {
|
||||
fieldArr = [];
|
||||
}
|
||||
fieldArr.push(val);
|
||||
categorizeMap.set(categorizeKey, fieldArr);
|
||||
};
|
||||
field.value.forEach((item) => {
|
||||
const itemInterface = getInterface(item?.interface);
|
||||
if (item?.primaryKey || item.isForeignKey) {
|
||||
addCategorizeVal('primaryAndForeignKey', item);
|
||||
return;
|
||||
}
|
||||
const group = itemInterface?.group as CategorizeKey;
|
||||
switch (group) {
|
||||
case 'systemInfo':
|
||||
case 'relation':
|
||||
addCategorizeVal(group, item);
|
||||
break;
|
||||
default:
|
||||
addCategorizeVal('basic', item);
|
||||
}
|
||||
});
|
||||
const tmpData: Array<CategorizeDataItem> = [];
|
||||
sortKeyArr.forEach((key) => {
|
||||
if (categorizeMap.get(key)?.length > 0) {
|
||||
tmpData.push({
|
||||
key,
|
||||
name: t(CategorizeKeyNameMap.get(key)),
|
||||
data: categorizeMap.get(key),
|
||||
});
|
||||
}
|
||||
});
|
||||
setCategorizeData(tmpData);
|
||||
},
|
||||
});
|
||||
const restProps = {
|
||||
rowSelection: props.rowSelection
|
||||
? {
|
||||
type: 'checkbox',
|
||||
selectedRowKeys,
|
||||
onChange(selectedRowKeys: any[]) {
|
||||
setSelectedRowKeys(selectedRowKeys);
|
||||
},
|
||||
...props.rowSelection,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const defaultRowKey = (record: any) => {
|
||||
return field.value?.indexOf?.(record);
|
||||
};
|
||||
|
||||
const expandedRowRender = (record: CategorizeDataItem, index, indent, expanded) => {
|
||||
debugger;
|
||||
return (
|
||||
<Table
|
||||
{...others}
|
||||
{...restProps}
|
||||
components={components}
|
||||
showHeader={true}
|
||||
columns={columns}
|
||||
dataSource={record.data}
|
||||
pagination={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
.ant-table {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Table
|
||||
showHeader={false}
|
||||
loading={props?.loading}
|
||||
columns={groupColumns}
|
||||
dataSource={categorizeData}
|
||||
pagination={false}
|
||||
expandable={{
|
||||
expandedRowRender,
|
||||
defaultExpandedRowKeys: sortKeyArr,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
@ -11,6 +11,7 @@ import { AddSubFieldAction } from './AddSubFieldAction';
|
||||
import { FieldSummary } from './components/FieldSummary';
|
||||
import { EditSubFieldAction } from './EditSubFieldAction';
|
||||
import { collectionSchema } from './schemas/collections';
|
||||
import { CollectionFieldsTable } from ".";
|
||||
|
||||
const useAsyncDataSource = (service: any) => (field: any) => {
|
||||
field.loading = true;
|
||||
@ -174,10 +175,12 @@ export const ConfigurationTable = () => {
|
||||
const { collections = [] } = useCollectionManager();
|
||||
const compile = useCompile();
|
||||
const loadCollections = async (field: any) => {
|
||||
return collections?.map((item: any) => ({
|
||||
label: compile(item.title),
|
||||
value: item.name,
|
||||
}));
|
||||
return collections
|
||||
?.filter((item) => !(item.autoCreate && item.isThrough))
|
||||
.map((item: any) => ({
|
||||
label: compile(item.title),
|
||||
value: item.name,
|
||||
}));
|
||||
};
|
||||
const ctx = useContext(SchemaComponentContext);
|
||||
return (
|
||||
@ -189,6 +192,7 @@ export const ConfigurationTable = () => {
|
||||
AddSubFieldAction,
|
||||
EditSubFieldAction,
|
||||
FieldSummary,
|
||||
CollectionFieldsTable
|
||||
}}
|
||||
scope={{
|
||||
useDestroySubField,
|
||||
|
@ -6,6 +6,7 @@ export * from './ConfigurationTable';
|
||||
export * from './EditFieldAction';
|
||||
export * from './interfaces';
|
||||
export * from './components';
|
||||
export * from './CollectionFieldsTable';
|
||||
|
||||
registerValidateFormats({
|
||||
uid: /^[A-Za-z0-9][A-Za-z0-9_-]*$/,
|
||||
|
@ -71,7 +71,7 @@ export const collectionFieldSchema: ISchema = {
|
||||
resource: 'collections.fields',
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 50,
|
||||
paginate: false,
|
||||
filter: {
|
||||
'interface.$not': null,
|
||||
},
|
||||
@ -123,7 +123,7 @@ export const collectionFieldSchema: ISchema = {
|
||||
table: {
|
||||
type: 'void',
|
||||
'x-uid': 'input',
|
||||
'x-component': 'Table.Void',
|
||||
'x-component': 'CollectionFieldsTable',
|
||||
'x-component-props': {
|
||||
rowKey: 'name',
|
||||
rowSelection: {
|
||||
|
@ -63,6 +63,11 @@ export const collectionSchema: ISchema = {
|
||||
pageSize: 50,
|
||||
filter: {
|
||||
inherit: false,
|
||||
options: {
|
||||
// filter auto create through collections
|
||||
autoCreate: { $not: true },
|
||||
isThrough: { $not: true },
|
||||
},
|
||||
},
|
||||
sort: ['sort'],
|
||||
appends: [],
|
||||
|
@ -78,7 +78,7 @@ export const reverseFieldProperties: Record<string, ISchema> = {
|
||||
properties: {
|
||||
autoCreateReverseField: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
default: false,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
'x-content': '{{t("Create inverse field in the target collection")}}',
|
||||
|
@ -121,6 +121,10 @@ export default {
|
||||
"Configure columns": "Configure columns",
|
||||
"Edit field": "Edit field",
|
||||
"Configure fields of {{title}}": "Configure fields of {{title}}",
|
||||
"Primary key & Foreign key fields": "Primary key & Foreign key fields",
|
||||
"Relation fields": "Relation fields",
|
||||
"System fields": "System fields",
|
||||
"General fields": "General fields",
|
||||
"Basic": "Basic",
|
||||
"Single line text": "Single line text",
|
||||
"Long text": "Long text",
|
||||
|
@ -125,6 +125,10 @@ export default {
|
||||
"Configure columns": "配置字段",
|
||||
"Edit field": "编辑字段",
|
||||
"Configure fields of {{title}}": "「{{title}}」的字段配置",
|
||||
"Primary key & Foreign key fields": "主外键字段",
|
||||
"Relation fields": "关系字段",
|
||||
"System fields": "系统字段",
|
||||
"General fields": "普通字段",
|
||||
"Basic": "基本类型",
|
||||
"Single line text": "单行文本",
|
||||
"Long text": "多行文本",
|
||||
|
@ -14,7 +14,7 @@ import { useCompile } from '../schema-component';
|
||||
import { BlockTemplatesPane } from '../schema-templates';
|
||||
import { SystemSettingsPane } from '../system-settings';
|
||||
|
||||
const SettingsCenterContext = createContext<any>({});
|
||||
export const SettingsCenterContext = createContext<any>({});
|
||||
|
||||
const PluginCard = (props) => {
|
||||
const history = useHistory<any>();
|
||||
|
@ -190,7 +190,7 @@ const InternalAdminLayout = (props: any) => {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 0;
|
||||
padding: 10px 50px;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
|
@ -74,7 +74,7 @@ export const useTableColumnInitializerFields = () => {
|
||||
const { name, fields = [] } = useCollection();
|
||||
const { getInterface } = useCollectionManager();
|
||||
return fields
|
||||
.filter((field) => field?.interface && field?.interface !== 'subTable')
|
||||
.filter((field) => field?.interface && field?.interface !== 'subTable' && !field?.isForeignKey)
|
||||
.map((field) => {
|
||||
const interfaceConfig = getInterface(field.interface);
|
||||
const schema = {
|
||||
@ -157,7 +157,7 @@ export const useFormItemInitializerFields = (options?: any) => {
|
||||
const { readPretty = form.readPretty, block = 'Form' } = options || {};
|
||||
|
||||
return fields
|
||||
?.filter((field) => field?.interface)
|
||||
?.filter((field) => field?.interface && !field?.isForeignKey)
|
||||
?.map((field) => {
|
||||
const interfaceConfig = getInterface(field.interface);
|
||||
|
||||
@ -433,7 +433,13 @@ export const useCollectionDataSourceItems = (componentName) => {
|
||||
type: 'itemGroup',
|
||||
title: t('Select collection'),
|
||||
children: collections
|
||||
?.filter((item) => !item.inherit)
|
||||
?.filter((item) => {
|
||||
if(item.inherit){
|
||||
return false
|
||||
}else{
|
||||
return !(item?.isThrough && item?.autoCreate);
|
||||
}
|
||||
})
|
||||
?.map((item, index) => {
|
||||
const templates = getTemplatesByCollection(item.name).filter((template) => {
|
||||
return (
|
||||
|
@ -2,7 +2,14 @@ import Database from '@nocobase/database';
|
||||
|
||||
export function afterCreateForForeignKeyField(db: Database) {
|
||||
function generateFkOptions(collectionName: string, foreignKey: string) {
|
||||
const M = db.getModel(collectionName);
|
||||
const collection = db.getCollection(collectionName);
|
||||
|
||||
if (!collection) {
|
||||
throw new Error('collection not found');
|
||||
}
|
||||
|
||||
const M = collection.model;
|
||||
|
||||
const attr = M.rawAttributes[foreignKey];
|
||||
if (!attr) {
|
||||
throw new Error(`${collectionName}.${foreignKey} does not exists`);
|
||||
@ -50,23 +57,39 @@ export function afterCreateForForeignKeyField(db: Database) {
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (instance) {
|
||||
if (instance.type !== values.type) {
|
||||
throw new Error(`fk type invalid`);
|
||||
}
|
||||
instance.set('sort',1);
|
||||
instance.set('sort', 1);
|
||||
instance.set('isForeignKey', true);
|
||||
await instance.save({ transaction });
|
||||
} else {
|
||||
await r.create({
|
||||
const creatInstance = await r.create({
|
||||
values: {
|
||||
isForeignKey: true,
|
||||
sort:1,
|
||||
...values,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
// SortField#setSortValue instance._previousDataValues[scopeKey] judgment cause create set sort:1 invalid, need update
|
||||
creatInstance.set('sort', 1);
|
||||
await creatInstance.save({ transaction });
|
||||
}
|
||||
// update ID sort:0
|
||||
await r.update({
|
||||
filter: {
|
||||
collectionName,
|
||||
options:{
|
||||
primaryKey: true
|
||||
}
|
||||
},
|
||||
values: {
|
||||
sort: 0,
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
return async (model, { transaction, context }) => {
|
||||
@ -74,7 +97,9 @@ export function afterCreateForForeignKeyField(db: Database) {
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { type, interface: interfaceType, collectionName, target, through, foreignKey, otherKey } = model.get();
|
||||
|
||||
// foreign key in target collection
|
||||
if (['oho', 'o2m'].includes(interfaceType)) {
|
||||
const values = generateFkOptions(target, foreignKey);
|
||||
@ -86,6 +111,7 @@ export function afterCreateForForeignKeyField(db: Database) {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
// foreign key in source collection
|
||||
else if (['obo', 'm2o'].includes(interfaceType)) {
|
||||
const values = generateFkOptions(collectionName, foreignKey);
|
||||
@ -94,6 +120,7 @@ export function afterCreateForForeignKeyField(db: Database) {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
// foreign key in through collection
|
||||
else if (['linkTo', 'm2m'].includes(interfaceType)) {
|
||||
if (type !== 'belongsToMany') {
|
||||
|
@ -0,0 +1,48 @@
|
||||
import { Migration } from '@nocobase/server';
|
||||
import { afterCreateForForeignKeyField } from '../hooks/afterCreateForForeignKeyField';
|
||||
|
||||
export default class DropForeignKeysMigration extends Migration {
|
||||
async up() {
|
||||
const result = await this.app.version.satisfies('<0.8.0');
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const transaction = await this.app.db.sequelize.transaction();
|
||||
const callback = afterCreateForForeignKeyField(this.app.db);
|
||||
|
||||
try {
|
||||
const fields = await this.app.db.getCollection('fields').repository.find({
|
||||
filter: {
|
||||
interface: {
|
||||
$in: ['oho', 'o2m', 'obo', 'm2o', 'linkTo', 'm2m'],
|
||||
},
|
||||
collectionName: {
|
||||
$not: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const field of fields) {
|
||||
try {
|
||||
await callback(field, {
|
||||
transaction,
|
||||
context: {},
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes('collection not found')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await transaction.rollback();
|
||||
}
|
||||
}
|
||||
}
|
@ -83,6 +83,7 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// after migrate
|
||||
this.app.db.on('fields.afterCreate', afterCreateForForeignKeyField(this.app.db));
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user