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); + }}> + + + +
+
{ + console.log(values); + const newVaules = [...value]; + if (typeof index === 'undefined') { + newVaules.push({...values, __id: generateIndex()}) + } else { + newVaules[index] = values; + } + onChange(newVaules); + console.log(newVaules); + }} ref={drawerRef}/> + { + + }} + components={components({ + data: { + list: value.map((item, index) => { + if (item.__id) { + return item; + }; + return {...item, __id: generateIndex()}; + }), + }, + mutate: (values) => { + onChange(values.list); + console.log(values); + }, + rowKey, + onMoved: async ({resourceKey, target}) => { + } + })} + onRow={(record, index) => ({ + onClick: () => { + console.log(record); + drawerRef.current.setVisible(true); + drawerRef.current.setIndex(index); + drawerRef.current.setData(record); + drawerRef.current.setTitle('编辑子字段'); + } + })} + pagination={false} + {...tableProps} + /> +
+ ); +} 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;