diff --git a/packages/app/src/api/collections/example.ts b/packages/app/src/api/collections/example.ts
index 5c61b0573..8bd99f6a9 100644
--- a/packages/app/src/api/collections/example.ts
+++ b/packages/app/src/api/collections/example.ts
@@ -60,6 +60,100 @@ export default {
showInForm: true,
},
},
+ {
+ interface: 'boolean',
+ title: '是/否',
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'select',
+ title: '下拉',
+ dataSource: [
+ {value: 'value1', label: '选项1'},
+ {value: 'value2', label: '选项2'},
+ {value: 'value3', label: '选项3'},
+ ],
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'multipleSelect',
+ title: '下拉多选',
+ dataSource: [
+ {value: 'value1', label: '选项1'},
+ {value: 'value2', label: '选项2'},
+ {value: 'value3', label: '选项3'},
+ ],
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'radio',
+ title: '单选框',
+ dataSource: [
+ {value: 'value1', label: '选项1'},
+ {value: 'value2', label: '选项2'},
+ {value: 'value3', label: '选项3'},
+ ],
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'checkboxes',
+ title: '多选框',
+ dataSource: [
+ {value: 'value1', label: '选项1'},
+ {value: 'value2', label: '选项2'},
+ {value: 'value3', label: '选项3'},
+ ],
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'subTable',
+ title: '子表格',
+ children: [
+ {
+ interface: 'string',
+ title: '标题',
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'textarea',
+ title: '内容',
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ ],
+ component: {
+ // showInTable: true,
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
{
interface: 'datetime',
title: '日期',
@@ -78,6 +172,24 @@ export default {
showInForm: true,
},
},
+ {
+ interface: 'createdBy',
+ title: '创建人',
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ // showInForm: true,
+ },
+ },
+ {
+ interface: 'updatedBy',
+ title: '更新人',
+ component: {
+ showInTable: true,
+ showInDetail: true,
+ // showInForm: true,
+ },
+ },
{
interface: 'createdAt',
title: '创建日期',
diff --git a/packages/app/src/api/index.ts b/packages/app/src/api/index.ts
index 757df5a67..9464847c0 100644
--- a/packages/app/src/api/index.ts
+++ b/packages/app/src/api/index.ts
@@ -6,9 +6,9 @@ import associated from '../../../actions/src/middlewares/associated';
import { Op } from 'sequelize';
const sync = {
- force: true,
+ force: false,
alter: {
- drop: true,
+ drop: false,
},
};
@@ -59,7 +59,7 @@ api.resourcer.use(async (ctx: actions.Context, next) => {
});
api.resourcer.use(async (ctx: actions.Context, next) => {
- const { resourceName } = ctx.action.params;
+ const { resourceName, fields = {} } = ctx.action.params;
const table = ctx.db.getTable(resourceName);
// ctx.state.developerMode = {[Op.not]: null};
ctx.state.developerMode = false;
@@ -67,8 +67,8 @@ api.resourcer.use(async (ctx: actions.Context, next) => {
ctx.action.setParam('filter.developerMode', ctx.state.developerMode);
}
if (table) {
- const except = [];
- const appends = [];
+ const except = fields.except || [];
+ const appends = fields.appends || [];
for (const [name, field] of table.getFields()) {
if (field.options.hidden) {
except.push(field.options.name);
diff --git a/packages/app/src/api/migrate.ts b/packages/app/src/api/migrate.ts
index cf8276024..7734b4fe5 100644
--- a/packages/app/src/api/migrate.ts
+++ b/packages/app/src/api/migrate.ts
@@ -173,17 +173,19 @@ api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-use
await Collection.import(table.getOptions(), { update: true, migrate: false });
}
await Page.import(data);
- await User.findOrCreate({
+ const user = await User.findOne({
where: {
username: "admin",
},
- defaults: {
+ });
+ if (!user) {
+ await User.create({
nickname: "超级管理员",
password: "admin",
username: "admin",
token: "38979f07e1fca68fb3d2",
- },
- });
+ });
+ }
await database.getModel('collections').import(require('./collections/example').default);
await database.close();
})();
diff --git a/packages/app/src/components/form.fields/drawer-select/index.tsx b/packages/app/src/components/form.fields/drawer-select/index.tsx
new file mode 100644
index 000000000..2db84c974
--- /dev/null
+++ b/packages/app/src/components/form.fields/drawer-select/index.tsx
@@ -0,0 +1,29 @@
+import React from 'react'
+import { connect } from '@formily/react-schema-renderer'
+import moment from 'moment'
+import { Select } from 'antd'
+import {
+ mapStyledProps,
+ mapTextComponent,
+ compose,
+ isStr,
+ isArr
+} from '../shared'
+
+function DrawerSelectComponent(props) {
+ console.log(props);
+ return (
+ <>
+
+ >
+ );
+}
+
+export const DrawerSelect = connect({
+ getProps: mapStyledProps,
+ getComponent: mapTextComponent,
+})(DrawerSelectComponent)
+
+export default DrawerSelect
diff --git a/packages/app/src/components/form.fields/registry.ts b/packages/app/src/components/form.fields/registry.ts
index 5b1d95f99..b430ff266 100644
--- a/packages/app/src/components/form.fields/registry.ts
+++ b/packages/app/src/components/form.fields/registry.ts
@@ -14,6 +14,8 @@ import { Range } from './range'
import { Rating } from './rating'
import { Upload } from './upload'
import { Filter } from './filter'
+import { DrawerSelect } from './drawer-select'
+import { SubTable } from './sub-table'
export const setup = () => {
registerFormFields({
@@ -43,5 +45,7 @@ export const setup = () => {
rating: Rating,
upload: Upload,
filter: Filter,
+ drawerSelect: DrawerSelect,
+ subTable: SubTable,
})
}
diff --git a/packages/app/src/components/form.fields/sub-table/Form.tsx b/packages/app/src/components/form.fields/sub-table/Form.tsx
new file mode 100644
index 000000000..8afd55c18
--- /dev/null
+++ b/packages/app/src/components/form.fields/sub-table/Form.tsx
@@ -0,0 +1,91 @@
+import React, { useState, useEffect, useImperativeHandle, forwardRef, useRef } from 'react';
+import { Button, Drawer } from 'antd';
+import { Tooltip, Input } from 'antd';
+import {
+ SchemaForm,
+ SchemaMarkupField as Field,
+ createFormActions,
+ createAsyncFormActions,
+ Submit,
+ Reset,
+ FormButtonGroup,
+ registerFormFields,
+ FormValidator,
+ setValidationLanguage,
+} from '@formily/antd';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { useRequest } from 'umi';
+import api from '@/api-client';
+import { Spin } from '@nocobase/client';
+
+export default forwardRef((props: any, ref) => {
+ console.log(props);
+ const {
+ target,
+ onFinish,
+ } = props;
+ const { data: schema = {}, loading } = useRequest(() => api.resource(target).getView({
+ resourceKey: 'form'
+ }));
+ const [visible, setVisible] = useState(false);
+ const [data, setData] = useState({});
+ const [title, setTitle] = useState('创建子字段');
+ const [index, setIndex] = useState();
+ useImperativeHandle(ref, () => ({
+ setVisible,
+ setData,
+ setTitle,
+ setIndex,
+ }));
+ const actions = createFormActions();
+ console.log({onFinish});
+ const { fields = {} } = schema;
+ if (loading) {
+ return ;
+ }
+ return (
+ {
+ setVisible(false);
+ }}
+ title={title}
+ footer={[
+
+ ]}
+ >
+
+
+
+ );
+ },
+ }}
+ >
+
+
+ );
+});
diff --git a/packages/app/src/components/form.fields/sub-table/Table.tsx b/packages/app/src/components/form.fields/sub-table/Table.tsx
new file mode 100644
index 000000000..94822c570
--- /dev/null
+++ b/packages/app/src/components/form.fields/sub-table/Table.tsx
@@ -0,0 +1,118 @@
+import React, { useEffect, useRef, useState } from 'react';
+import ReactDOM from 'react-dom';
+import { Table as AntdTable, Button, Space, Popconfirm } from 'antd';
+import { Actions } from '@/components/actions';
+import ViewFactory from '@/components/views';
+import { useRequest } from 'umi';
+import api from '@/api-client';
+import { components, fields2columns } from '@/components/views/SortableTable';
+import Form from './Form';
+import { Spin } from '@nocobase/client';
+import maxBy from 'lodash/maxBy';
+
+export interface SimpleTableProps {
+ schema?: any;
+ activeTab?: any;
+ resourceName: string;
+ associatedName?: string;
+ associatedKey?: string;
+ [key: string]: any;
+}
+
+export function generateIndex(): string {
+ return `${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`;
+}
+
+export default function Table(props: SimpleTableProps) {
+ console.log(props);
+ const drawerRef = useRef();
+ const [selectedRowKeys, setSelectedRowKeys] = useState([]);
+ const onTableChange = (selectedRowKeys: React.ReactText[]) => {
+ setSelectedRowKeys(selectedRowKeys);
+ }
+ const tableProps: any = {};
+ tableProps.rowSelection = {
+ selectedRowKeys,
+ onChange: onTableChange,
+ }
+ const { rowKey = '__id', fields = [] } = props;
+ const { target = 'fields', value = [], onChange } = props;
+
+ const { data: schema = {}, loading } = useRequest(() => api.resource(target).getView({
+ resourceKey: 'simple'
+ }));
+
+ if (loading) {
+ return
+ }
+
+ return (
+
+
+
+
+ {
+ console.log({selectedRowKeys})
+ const newValues = value.filter(item => selectedRowKeys.indexOf(item.__id) === -1);
+ onChange(newValues);
+ }}>
+
+
+
+
+
+ );
+}
diff --git a/packages/app/src/components/form.fields/sub-table/index.tsx b/packages/app/src/components/form.fields/sub-table/index.tsx
new file mode 100644
index 000000000..6ec6cf4b8
--- /dev/null
+++ b/packages/app/src/components/form.fields/sub-table/index.tsx
@@ -0,0 +1,20 @@
+import React, { useRef } from 'react'
+import { connect } from '@formily/react-schema-renderer'
+import moment from 'moment'
+import { Select, Button, Table as AntdTable } from 'antd'
+import {
+ mapStyledProps,
+ mapTextComponent,
+ compose,
+ isStr,
+ isArr
+} from '../shared'
+import ViewFactory from '@/components/views';
+import Table from './Table';
+
+export const SubTable = connect({
+ getProps: mapStyledProps,
+ getComponent: mapTextComponent,
+})(Table)
+
+export default SubTable
diff --git a/packages/app/src/components/views/Details.tsx b/packages/app/src/components/views/Details.tsx
index 15d8b2d30..c97a63d1a 100644
--- a/packages/app/src/components/views/Details.tsx
+++ b/packages/app/src/components/views/Details.tsx
@@ -17,15 +17,16 @@ export function Details(props: any) {
associatedKey,
resourceKey,
} = props;
+ const { actions = [], fields = [] } = props.schema;
const { data = {}, loading, refresh } = useRequest(() => {
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
return api.resource(name).get({
resourceKey,
associatedKey,
+ 'fields[appends]': fields.filter(field => get(field, 'interface') === 'subTable').map(field => field.name).join(',')
});
});
console.log(props);
- const { actions = [], fields = [] } = props.schema;
return (
{fields.map((field: any) => {
return (
-
+
)
diff --git a/packages/app/src/components/views/Field/index.tsx b/packages/app/src/components/views/Field/index.tsx
index 595bead69..53ecb1d9b 100644
--- a/packages/app/src/components/views/Field/index.tsx
+++ b/packages/app/src/components/views/Field/index.tsx
@@ -1,10 +1,11 @@
import React, { useEffect, useState } from 'react';
import moment from 'moment';
-import { Tag, Popover } from 'antd';
+import { Tag, Popover, Table } from 'antd';
import Icon from '@/components/icons';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
+import { fields2columns } from '../SortableTable';
const InterfaceTypes = new Map();
@@ -27,6 +28,12 @@ function getFieldComponent(type) {
export function StringField(props: any) {
const { value } = props;
+ if (!value) {
+ return null;
+ }
+ if (typeof value === 'object') {
+ return JSON.stringify(value);
+ }
return (
<>{value}>
);
@@ -157,6 +164,19 @@ export function RealtionField(props: any) {
);
}
+export function SubTableField(props: any) {
+ const { schema: { children }, value } = props;
+ console.log(value);
+ if (!Array.isArray(value)) {
+ return null;
+ }
+ return (
+
+
+
+ );
+}
+
registerFieldComponents({
string: StringField,
textarea: TextareaField,
@@ -173,6 +193,7 @@ registerFieldComponents({
icon: IconField,
createdBy: RealtionField,
updatedBy: RealtionField,
+ subTable: SubTableField,
});
export default function Field(props: any) {
diff --git a/packages/plugin-collections/src/__tests__/index.ts b/packages/plugin-collections/src/__tests__/index.ts
index 3c9e9c581..13f718634 100644
--- a/packages/plugin-collections/src/__tests__/index.ts
+++ b/packages/plugin-collections/src/__tests__/index.ts
@@ -59,9 +59,7 @@ export async function getApp() {
app.resourcer.registerActionHandlers({...actions.associate, ...actions.common});
app.registerPlugin('collections', [plugin]);
await app.loadPlugins();
- await app.database.sync({
- force: true,
- });
+ await app.database.sync();
// 表配置信息存到数据库里
// const tables = app.database.getTables([]);
// for (const table of tables) {
diff --git a/packages/plugin-collections/src/__tests__/models/field.test.ts b/packages/plugin-collections/src/__tests__/models/field.test.ts
index eb08b021b..57c1df1cb 100644
--- a/packages/plugin-collections/src/__tests__/models/field.test.ts
+++ b/packages/plugin-collections/src/__tests__/models/field.test.ts
@@ -50,4 +50,80 @@ describe('models.field', () => {
{label: 'xx', value: 'xx'},
]);
});
+
+ it.only('sub table field', async () => {
+ const [Collection, Field] = app.database.getModels(['collections', 'fields']);
+ const options = {
+ title: 'tests',
+ name: 'tests',
+ fields: [
+ {
+ interface: 'subTable',
+ title: '子表格',
+ name: 'subs',
+ children: [
+ {
+ interface: 'string',
+ title: '名称',
+ name: 'name',
+ },
+ ],
+ },
+ ],
+ };
+ const collection = await Collection.create(options);
+ await collection.updateAssociations(options);
+ const field = await Field.findOne({
+ where: {
+ title: '子表格',
+ },
+ });
+ await field.createChild({
+ interface: 'string',
+ title: '名称',
+ name: 'title',
+ });
+ const Test = app.database.getModel('tests');
+ const Sub = app.database.getModel('subs');
+ // console.log(Test.associations);
+ // console.log(Sub.rawAttributes);
+ const test = await Test.create({});
+ const sub = await test.createSub({name: 'name1', title: 'title1'});
+ expect(sub.toJSON()).toMatchObject({name: 'name1', title: 'title1'})
+ });
+
+ it('sub table field', async () => {
+ const [Collection, Field] = app.database.getModels(['collections', 'fields']);
+ // @ts-ignore
+ const options = {
+ title: 'tests',
+ name: 'tests',
+ fields: [
+ {
+ interface: 'subTable',
+ title: '子表格',
+ // name: 'subs',
+ children: [
+ {
+ interface: 'string',
+ title: '名称',
+ // name: 'name',
+ },
+ ],
+ },
+ ],
+ };
+ const collection = await Collection.create(options);
+ await collection.updateAssociations(options);
+ const field = await Field.findOne({
+ where: {
+ title: '子表格',
+ },
+ });
+ await field.createChild({
+ interface: 'string',
+ title: '名称',
+ name: 'title',
+ });
+ });
});
diff --git a/packages/plugin-collections/src/collections/collections.ts b/packages/plugin-collections/src/collections/collections.ts
index cd364e2d0..fe7934734 100644
--- a/packages/plugin-collections/src/collections/collections.ts
+++ b/packages/plugin-collections/src/collections/collections.ts
@@ -238,6 +238,11 @@ export default {
list: {
sort: 'sort',
},
+ get: {
+ fields: {
+ appends: ['children'],
+ },
+ },
},
component: {
type: 'drawerSelect',
diff --git a/packages/plugin-collections/src/collections/fields.ts b/packages/plugin-collections/src/collections/fields.ts
index 4766879ec..bf354d821 100644
--- a/packages/plugin-collections/src/collections/fields.ts
+++ b/packages/plugin-collections/src/collections/fields.ts
@@ -90,6 +90,36 @@ export default {
"target": "timeFormat",
"condition": "{{ ['time'].indexOf($self.value) !== -1 }}"
},
+ {
+ "type": "value:visible",
+ "target": "multiple",
+ "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
+ },
+ {
+ "type": "value:visible",
+ "target": "target",
+ "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
+ },
+ {
+ "type": "value:visible",
+ "target": "labelField",
+ "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
+ },
+ {
+ "type": "value:visible",
+ "target": "createable",
+ "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
+ },
+ {
+ "type": "value:visible",
+ "target": "children",
+ "condition": "{{ ['subTable'].indexOf($self.value) !== -1 }}"
+ },
+ {
+ "type": "value:visible",
+ "target": "component.showInTable",
+ "condition": "{{ ['subTable'].indexOf($self.value) === -1 }}"
+ },
{
"type": "value:visible",
"target": "component.showInForm",
@@ -158,7 +188,6 @@ export default {
type: 'virtual',
name: 'precision',
title: '精度',
- defaultValue: 1,
dataSource: [
{value: 1, label: '1'},
{value: 0.1, label: '1.0'},
@@ -169,6 +198,7 @@ export default {
component: {
type: 'number',
showInForm: true,
+ default: 1,
},
},
{
@@ -176,7 +206,6 @@ export default {
type: 'virtual',
name: 'dateFormat',
title: '日期格式',
- defaultValue: 'YYYY/MM/DD',
dataSource: [
{value: 'YYYY/MM/DD', label: '年/月/日'},
{value: 'YYYY-MM-DD', label: '年-月-日'},
@@ -185,6 +214,7 @@ export default {
component: {
type: 'string',
showInForm: true,
+ default: 'YYYY/MM/DD',
},
},
{
@@ -192,10 +222,10 @@ export default {
type: 'virtual',
name: 'showTime',
title: '显示时间',
- defaultValue: false,
component: {
type: 'boolean',
showInForm: true,
+ default: false,
"x-linkages": [
{
"type": "value:visible",
@@ -210,7 +240,6 @@ export default {
type: 'virtual',
name: 'timeFormat',
title: '时间格式',
- defaultValue: 'HH:mm:ss',
dataSource: [
{ value: 'HH:mm:ss', label: '24小时制' },
{ value: 'hh:mm:ss a', label: '12小时制' },
@@ -218,6 +247,7 @@ export default {
component: {
type: 'string',
showInForm: true,
+ default: 'HH:mm:ss',
},
},
{
@@ -234,18 +264,80 @@ export default {
},
},
{
- interface: 'linkTo',
- multiple: true,
- type: 'hasMany',
- name: 'children',
- title: '子字段',
- target: 'fields',
- foreignKey: 'parent_id',
- sourceKey: 'id',
+ interface: 'string',
+ type: 'virtual',
+ name: 'target',
+ title: '要关联的数据表',
component: {
type: 'drawerSelect',
+ showInDetail: true,
+ showInForm: true,
},
},
+ {
+ interface: 'string',
+ type: 'virtual',
+ name: 'labelField',
+ title: '要关联的字段',
+ component: {
+ type: 'drawerSelect',
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'boolean',
+ type: 'virtual',
+ name: 'multiple',
+ title: '允许添加多条记录',
+ component: {
+ type: 'checkbox',
+ showInDetail: true,
+ showInForm: true,
+ default: true,
+ },
+ },
+ {
+ interface: 'boolean',
+ type: 'virtual',
+ name: 'createable',
+ title: '允许直接在关联的数据表内新建数据',
+ component: {
+ type: 'checkbox',
+ showInDetail: true,
+ showInForm: true,
+ },
+ },
+ {
+ interface: 'subTable',
+ type: 'hasMany',
+ name: 'children',
+ target: 'fields',
+ sourceKey: 'id',
+ foreignKey: 'parent_id',
+ title: '子表格字段',
+ // visible: true,
+ component: {
+ type: 'subTable',
+ default: [],
+ // showInTable: true,
+ // showInDetail: true,
+ showInForm: true,
+ },
+ },
+ // {
+ // interface: 'linkTo',
+ // multiple: true,
+ // type: 'hasMany',
+ // name: 'children',
+ // title: '子字段',
+ // target: 'fields',
+ // foreignKey: 'parent_id',
+ // sourceKey: 'id',
+ // component: {
+ // type: 'drawerSelect',
+ // },
+ // },
{
interface: 'textarea',
type: 'virtual',
diff --git a/packages/plugin-collections/src/collections/tabs.ts b/packages/plugin-collections/src/collections/tabs.ts
index 025aa5e5c..0588ec0f9 100644
--- a/packages/plugin-collections/src/collections/tabs.ts
+++ b/packages/plugin-collections/src/collections/tabs.ts
@@ -55,7 +55,7 @@ export default {
required: true,
dataSource: [
{ label: '详情数据', value: 'details' },
- { label: '相关数据', value: 'association', disabled: true },
+ { label: '相关数据', value: 'association' },
{ label: '模块组合', value: 'module', disabled: true },
],
component: {
@@ -78,7 +78,7 @@ export default {
name: 'association',
title: '相关数据表',
component: {
- type: 'string',
+ type: 'drawerSelect',
showInDetail: true,
showInForm: true,
},
diff --git a/packages/plugin-collections/src/collections/views.ts b/packages/plugin-collections/src/collections/views.ts
index 08dbe7ee4..438256a88 100644
--- a/packages/plugin-collections/src/collections/views.ts
+++ b/packages/plugin-collections/src/collections/views.ts
@@ -26,6 +26,7 @@ export default {
type: 'string',
name: 'title',
title: '视图名称',
+ required: true,
component: {
type: 'string',
className: 'drag-visible',
@@ -51,9 +52,10 @@ export default {
type: 'string',
name: 'type',
title: '视图类型',
+ required: true,
dataSource: [
{ label: '表格', value: 'table' },
- { label: '表单', value: 'form' },
+ // { label: '表单', value: 'form' },
{ label: '看板', value: 'kanban', disabled: true },
{ label: '日历', value: 'calendar', disabled: true },
{ label: '地图', value: 'map', disabled: true },
@@ -63,6 +65,7 @@ export default {
showInTable: true,
showInDetail: true,
showInForm: true,
+ default: 'table',
"x-linkages": [
{
"type": "value:visible",
@@ -79,6 +82,7 @@ export default {
title: '筛选数据',
developerMode: false,
mode: 'replace',
+ defaultValue: {},
component: {
type: 'filter',
showInForm: true,
@@ -89,13 +93,15 @@ export default {
type: 'string',
name: 'template',
title: '模板',
+ required: true,
dataSource: [
- { label: '表单', value: 'DrawerForm' },
+ // { label: '表单', value: 'DrawerForm' },
{ label: '常规表格', value: 'Table' },
{ label: '简易表格', value: 'SimpleTable' },
],
component: {
type: 'string',
+ default: 'Table',
showInTable: true,
showInDetail: true,
showInForm: true,
diff --git a/packages/plugin-collections/src/hooks/fields-after-create.ts b/packages/plugin-collections/src/hooks/fields-after-create.ts
index 9c4dfb44f..b035d03ee 100644
--- a/packages/plugin-collections/src/hooks/fields-after-create.ts
+++ b/packages/plugin-collections/src/hooks/fields-after-create.ts
@@ -5,4 +5,11 @@ export default async function (model: FieldModel, options: any = {}) {
if (migrate) {
await model.migrate(options);
}
+ if (model.get('collection_name') && model.get('parent_id')) {
+ const parent = await model.getParent({
+ ...options,
+ });
+ const Collection = model.database.getModel('collections');
+ await Collection.load({...options, where: {name: parent.get('collection_name')}});
+ }
}
diff --git a/packages/plugin-collections/src/hooks/fields-before-validate.ts b/packages/plugin-collections/src/hooks/fields-before-validate.ts
index d82109c3e..4d74ba9a4 100644
--- a/packages/plugin-collections/src/hooks/fields-before-validate.ts
+++ b/packages/plugin-collections/src/hooks/fields-before-validate.ts
@@ -1,11 +1,51 @@
import FieldModel from '../models/field';
-import * as types from '../interfaces/types';
import _ from 'lodash';
-export default async function (model: FieldModel) {
- // if (model.get('interface')) {
- // model.setInterface(model.get('interface'));
- // }
+export default async function (model: FieldModel, options) {
// 生成随机 name 要放最后
- model.generateNameIfNull();
+ // model.generateNameIfNull();
+ const Collection = model.database.getModel('collections');
+ if (model.get('interface') === 'subTable') {
+ const target = model.get('target');
+ if (target) {
+ const collection = await Collection.findOne({
+ ...options,
+ where: {
+ name: target,
+ },
+ });
+ if (!collection) {
+ await Collection.create({
+ name: target,
+ internal: true,
+ developerMode: true,
+ }, options);
+ }
+ await Collection.load({...options, where: {name: model.get('name')}})
+ }
+ }
+ // 如果 collection_name 不存在
+ if (!model.get('collection_name') && model.get('parent_id')) {
+ const parent = await model.getParent({
+ ...options,
+ });
+ const target = parent.get('target');
+ if (target) {
+ const collection = await Collection.findOne({
+ ...options,
+ where: {
+ name: target,
+ },
+ });
+ if (!collection) {
+ await Collection.create({
+ name: target,
+ internal: true,
+ developerMode: true,
+ }, options);
+ }
+ await Collection.load({...options, where: {name: parent.get('name')}})
+ model.set('collection_name', target);
+ }
+ }
}
diff --git a/packages/plugin-collections/src/interfaces/types.ts b/packages/plugin-collections/src/interfaces/types.ts
index 3769584fc..84fd8fd6f 100644
--- a/packages/plugin-collections/src/interfaces/types.ts
+++ b/packages/plugin-collections/src/interfaces/types.ts
@@ -270,13 +270,13 @@ export const time = {
// });
export const subTable = {
title: '子表格',
- disabled: true,
+ // disabled: true,
options: {
interface: 'subTable',
type: 'hasMany',
// fields: [],
component: {
- type: 'table',
+ type: 'subTable',
},
},
};
@@ -324,14 +324,14 @@ export const subTable = {
export const linkTo = {
title: '关联数据',
- disabled: true,
+ // disabled: true,
options: {
interface: 'linkTo',
multiple: true, // 可能影响 type
type: 'belongsToMany',
// name,
// target: '关联表', // 用户会输入
- filterable: true,
+ filterable: false,
component: {
type: 'drawerSelect',
},
diff --git a/packages/plugin-collections/src/models/collection.ts b/packages/plugin-collections/src/models/collection.ts
index 1ed5216bb..0c7f07e3b 100644
--- a/packages/plugin-collections/src/models/collection.ts
+++ b/packages/plugin-collections/src/models/collection.ts
@@ -61,9 +61,9 @@ export class CollectionModel extends BaseModel {
const associationTableNames = [];
for (const [key, association] of table.getAssociations()) {
// TODO:是否需要考虑重载的情况?(暂时是跳过处理)
- if (!this.database.isDefined(association.options.target)) {
- continue;
- }
+ // if (!this.database.isDefined(association.options.target)) {
+ // continue;
+ // }
associationTableNames.push(association.options.target);
}
if (associationTableNames.length) {
@@ -131,7 +131,7 @@ export class CollectionModel extends BaseModel {
data = _.cloneDeep(data);
// @ts-ignore
const { update } = options;
- let collection;
+ let collection: CollectionModel;
if (data.name) {
collection = await this.findOne({
...options,
@@ -159,6 +159,7 @@ export class CollectionModel extends BaseModel {
continue;
}
const Model = this.database.getModel(key);
+ const ids = [];
for (const index in data[key]) {
let model;
const item = data[key][index];
@@ -189,6 +190,14 @@ export class CollectionModel extends BaseModel {
collection_name: collection.name,
}, options);
}
+ if (model) {
+ ids.push(model.id);
+ }
+ }
+ if (collection.get('internal')) {
+ await collection.updateAssociations({
+ [key]: ids,
+ });
}
}
return collection;
diff --git a/packages/plugin-collections/src/models/field.ts b/packages/plugin-collections/src/models/field.ts
index e70e298f0..59d6c40c2 100644
--- a/packages/plugin-collections/src/models/field.ts
+++ b/packages/plugin-collections/src/models/field.ts
@@ -4,6 +4,8 @@ import { FieldOptions } from '@nocobase/database';
import * as types from '../interfaces/types';
import { merge } from '../utils';
import { BuildOptions } from 'sequelize';
+import { SaveOptions, Utils } from 'sequelize';
+import { generateCollectionName } from './collection';
export function generateFieldName(title?: string): string {
return `f_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`;
@@ -23,10 +25,21 @@ export class FieldModel extends BaseModel {
let args = [options, data];
// @ts-ignore
data = merge(...args);
+ if (['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(data.type)) {
+ if (!data.name) {
+ data.name = generateFieldName();
+ data.target = generateCollectionName();
+ }
+ if (!data.target) {
+ data.target = ['hasOne', 'belongsTo'].includes(data.type) ? Utils.pluralize(data.name) : data.name;
+ }
+ }
+ if (!data.name) {
+ data.name = generateFieldName();
+ }
}
// @ts-ignore
super(data, options);
- // console.log(data);
}
generateName() {
diff --git a/packages/plugin-pages/src/actions/getView.ts b/packages/plugin-pages/src/actions/getView.ts
index 5088c5a26..ed8b773e6 100644
--- a/packages/plugin-pages/src/actions/getView.ts
+++ b/packages/plugin-pages/src/actions/getView.ts
@@ -79,6 +79,10 @@ const transforms = {
if (interfaceType === 'multipleSelect') {
set(prop, 'x-component-props.mode', 'multiple');
}
+ if (interfaceType === 'subTable' && field.get('target')) {
+ set(prop, 'x-component-props.target', field.get('target'));
+ // resourceName
+ }
if (['radio', 'select', 'multipleSelect', 'checkboxes'].includes(interfaceType)) {
prop.enum = field.get('dataSource');
}
@@ -94,9 +98,14 @@ const transforms = {
if (!get(field.component, 'showInDetail')) {
continue;
}
+ const props = {};
+ if (field.get('interface') === 'subTable') {
+ const children = await field.getChildren();
+ props['children'] = children.map(child => ({...child.toJSON(), dataIndex: child.name.split('.')}))
+ }
arr.push({
...field.toJSON(),
- ...field.options,
+ ...props,
});
}
return arr;