feat: support subTable field
* feat: add linkTo and subTable fields * add subTable field component * improve sub table * bugfix
This commit is contained in:
parent
3054ddb13b
commit
ecab106c3c
@ -60,6 +60,100 @@ export default {
|
|||||||
showInForm: true,
|
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',
|
interface: 'datetime',
|
||||||
title: '日期',
|
title: '日期',
|
||||||
@ -78,6 +172,24 @@ export default {
|
|||||||
showInForm: true,
|
showInForm: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
interface: 'createdBy',
|
||||||
|
title: '创建人',
|
||||||
|
component: {
|
||||||
|
showInTable: true,
|
||||||
|
showInDetail: true,
|
||||||
|
// showInForm: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
interface: 'updatedBy',
|
||||||
|
title: '更新人',
|
||||||
|
component: {
|
||||||
|
showInTable: true,
|
||||||
|
showInDetail: true,
|
||||||
|
// showInForm: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
interface: 'createdAt',
|
interface: 'createdAt',
|
||||||
title: '创建日期',
|
title: '创建日期',
|
||||||
|
@ -6,9 +6,9 @@ import associated from '../../../actions/src/middlewares/associated';
|
|||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
|
|
||||||
const sync = {
|
const sync = {
|
||||||
force: true,
|
force: false,
|
||||||
alter: {
|
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) => {
|
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);
|
const table = ctx.db.getTable(resourceName);
|
||||||
// ctx.state.developerMode = {[Op.not]: null};
|
// ctx.state.developerMode = {[Op.not]: null};
|
||||||
ctx.state.developerMode = false;
|
ctx.state.developerMode = false;
|
||||||
@ -67,8 +67,8 @@ api.resourcer.use(async (ctx: actions.Context, next) => {
|
|||||||
ctx.action.setParam('filter.developerMode', ctx.state.developerMode);
|
ctx.action.setParam('filter.developerMode', ctx.state.developerMode);
|
||||||
}
|
}
|
||||||
if (table) {
|
if (table) {
|
||||||
const except = [];
|
const except = fields.except || [];
|
||||||
const appends = [];
|
const appends = fields.appends || [];
|
||||||
for (const [name, field] of table.getFields()) {
|
for (const [name, field] of table.getFields()) {
|
||||||
if (field.options.hidden) {
|
if (field.options.hidden) {
|
||||||
except.push(field.options.name);
|
except.push(field.options.name);
|
||||||
|
@ -173,17 +173,19 @@ api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-use
|
|||||||
await Collection.import(table.getOptions(), { update: true, migrate: false });
|
await Collection.import(table.getOptions(), { update: true, migrate: false });
|
||||||
}
|
}
|
||||||
await Page.import(data);
|
await Page.import(data);
|
||||||
await User.findOrCreate({
|
const user = await User.findOne({
|
||||||
where: {
|
where: {
|
||||||
username: "admin",
|
username: "admin",
|
||||||
},
|
},
|
||||||
defaults: {
|
});
|
||||||
|
if (!user) {
|
||||||
|
await User.create({
|
||||||
nickname: "超级管理员",
|
nickname: "超级管理员",
|
||||||
password: "admin",
|
password: "admin",
|
||||||
username: "admin",
|
username: "admin",
|
||||||
token: "38979f07e1fca68fb3d2",
|
token: "38979f07e1fca68fb3d2",
|
||||||
},
|
});
|
||||||
});
|
}
|
||||||
await database.getModel('collections').import(require('./collections/example').default);
|
await database.getModel('collections').import(require('./collections/example').default);
|
||||||
await database.close();
|
await database.close();
|
||||||
})();
|
})();
|
||||||
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<Select>
|
||||||
|
<Select.Option value={'aaa'}>aaa</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DrawerSelect = connect({
|
||||||
|
getProps: mapStyledProps,
|
||||||
|
getComponent: mapTextComponent,
|
||||||
|
})(DrawerSelectComponent)
|
||||||
|
|
||||||
|
export default DrawerSelect
|
@ -14,6 +14,8 @@ import { Range } from './range'
|
|||||||
import { Rating } from './rating'
|
import { Rating } from './rating'
|
||||||
import { Upload } from './upload'
|
import { Upload } from './upload'
|
||||||
import { Filter } from './filter'
|
import { Filter } from './filter'
|
||||||
|
import { DrawerSelect } from './drawer-select'
|
||||||
|
import { SubTable } from './sub-table'
|
||||||
|
|
||||||
export const setup = () => {
|
export const setup = () => {
|
||||||
registerFormFields({
|
registerFormFields({
|
||||||
@ -43,5 +45,7 @@ export const setup = () => {
|
|||||||
rating: Rating,
|
rating: Rating,
|
||||||
upload: Upload,
|
upload: Upload,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
|
drawerSelect: DrawerSelect,
|
||||||
|
subTable: SubTable,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
91
packages/app/src/components/form.fields/sub-table/Form.tsx
Normal file
91
packages/app/src/components/form.fields/sub-table/Form.tsx
Normal file
@ -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 <Spin/>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
{...props}
|
||||||
|
destroyOnClose
|
||||||
|
visible={visible}
|
||||||
|
width={'40%'}
|
||||||
|
onClose={() => {
|
||||||
|
setVisible(false);
|
||||||
|
}}
|
||||||
|
title={title}
|
||||||
|
footer={[
|
||||||
|
<Button type={'primary'} onClick={async () => {
|
||||||
|
const { values = {} } = await actions.submit();
|
||||||
|
setVisible(false);
|
||||||
|
onFinish && onFinish(values, index);
|
||||||
|
}}>提交</Button>
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<SchemaForm
|
||||||
|
colon={true}
|
||||||
|
layout={'vertical'}
|
||||||
|
initialValues={data}
|
||||||
|
actions={actions}
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
properties: fields,
|
||||||
|
}}
|
||||||
|
expressionScope={{
|
||||||
|
text(...args: any[]) {
|
||||||
|
return React.createElement('span', {}, ...args)
|
||||||
|
},
|
||||||
|
tooltip(title: string, offset = 3) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={title}>
|
||||||
|
<QuestionCircleOutlined
|
||||||
|
style={{ margin: '0 3px', cursor: 'default', marginLeft: offset }}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
</SchemaForm>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
});
|
118
packages/app/src/components/form.fields/sub-table/Table.tsx
Normal file
118
packages/app/src/components/form.fields/sub-table/Table.tsx
Normal file
@ -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<any>();
|
||||||
|
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 <Spin/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Space style={{marginBottom: 14}}>
|
||||||
|
<Button type={'primary'} onClick={() => {
|
||||||
|
drawerRef.current.setVisible(true);
|
||||||
|
drawerRef.current.setIndex(undefined);
|
||||||
|
drawerRef.current.setData({});
|
||||||
|
drawerRef.current.setTitle('创建子字段');
|
||||||
|
}}>创建</Button>
|
||||||
|
<Popconfirm title="确认删除吗?" onConfirm={() => {
|
||||||
|
console.log({selectedRowKeys})
|
||||||
|
const newValues = value.filter(item => selectedRowKeys.indexOf(item.__id) === -1);
|
||||||
|
onChange(newValues);
|
||||||
|
}}>
|
||||||
|
<Button>删除</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<Form target={target} onFinish={(values, index: number) => {
|
||||||
|
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}/>
|
||||||
|
<AntdTable
|
||||||
|
rowKey={rowKey}
|
||||||
|
// loading={loading}
|
||||||
|
columns={fields2columns(schema.fields||[])}
|
||||||
|
dataSource={value}
|
||||||
|
onChange={(pagination, filters, sorter, extra) => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
20
packages/app/src/components/form.fields/sub-table/index.tsx
Normal file
20
packages/app/src/components/form.fields/sub-table/index.tsx
Normal file
@ -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
|
@ -17,15 +17,16 @@ export function Details(props: any) {
|
|||||||
associatedKey,
|
associatedKey,
|
||||||
resourceKey,
|
resourceKey,
|
||||||
} = props;
|
} = props;
|
||||||
|
const { actions = [], fields = [] } = props.schema;
|
||||||
const { data = {}, loading, refresh } = useRequest(() => {
|
const { data = {}, loading, refresh } = useRequest(() => {
|
||||||
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
|
||||||
return api.resource(name).get({
|
return api.resource(name).get({
|
||||||
resourceKey,
|
resourceKey,
|
||||||
associatedKey,
|
associatedKey,
|
||||||
|
'fields[appends]': fields.filter(field => get(field, 'interface') === 'subTable').map(field => field.name).join(',')
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
console.log(props);
|
console.log(props);
|
||||||
const { actions = [], fields = [] } = props.schema;
|
|
||||||
return (
|
return (
|
||||||
<Card bordered={false}>
|
<Card bordered={false}>
|
||||||
<Actions
|
<Actions
|
||||||
@ -40,7 +41,7 @@ export function Details(props: any) {
|
|||||||
<Descriptions bordered column={1}>
|
<Descriptions bordered column={1}>
|
||||||
{fields.map((field: any) => {
|
{fields.map((field: any) => {
|
||||||
return (
|
return (
|
||||||
<Descriptions.Item labelStyle={{minWidth: 200}} label={field.title||field.name}>
|
<Descriptions.Item labelStyle={{minWidth: 200, maxWidth: 300, width: 300}} label={field.title||field.name}>
|
||||||
<Field viewType={'descriptions'} schema={field} value={get(data, field.name)}/>
|
<Field viewType={'descriptions'} schema={field} value={get(data, field.name)}/>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
)
|
)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Tag, Popover } from 'antd';
|
import { Tag, Popover, Table } from 'antd';
|
||||||
import Icon from '@/components/icons';
|
import Icon from '@/components/icons';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { fields2columns } from '../SortableTable';
|
||||||
|
|
||||||
const InterfaceTypes = new Map<string, any>();
|
const InterfaceTypes = new Map<string, any>();
|
||||||
|
|
||||||
@ -27,6 +28,12 @@ function getFieldComponent(type) {
|
|||||||
|
|
||||||
export function StringField(props: any) {
|
export function StringField(props: any) {
|
||||||
const { value } = props;
|
const { value } = props;
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>{value}</>
|
<>{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 (
|
||||||
|
<div>
|
||||||
|
<Table columns={fields2columns(children)} dataSource={value} pagination={false}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
registerFieldComponents({
|
registerFieldComponents({
|
||||||
string: StringField,
|
string: StringField,
|
||||||
textarea: TextareaField,
|
textarea: TextareaField,
|
||||||
@ -173,6 +193,7 @@ registerFieldComponents({
|
|||||||
icon: IconField,
|
icon: IconField,
|
||||||
createdBy: RealtionField,
|
createdBy: RealtionField,
|
||||||
updatedBy: RealtionField,
|
updatedBy: RealtionField,
|
||||||
|
subTable: SubTableField,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Field(props: any) {
|
export default function Field(props: any) {
|
||||||
|
@ -59,9 +59,7 @@ export async function getApp() {
|
|||||||
app.resourcer.registerActionHandlers({...actions.associate, ...actions.common});
|
app.resourcer.registerActionHandlers({...actions.associate, ...actions.common});
|
||||||
app.registerPlugin('collections', [plugin]);
|
app.registerPlugin('collections', [plugin]);
|
||||||
await app.loadPlugins();
|
await app.loadPlugins();
|
||||||
await app.database.sync({
|
await app.database.sync();
|
||||||
force: true,
|
|
||||||
});
|
|
||||||
// 表配置信息存到数据库里
|
// 表配置信息存到数据库里
|
||||||
// const tables = app.database.getTables([]);
|
// const tables = app.database.getTables([]);
|
||||||
// for (const table of tables) {
|
// for (const table of tables) {
|
||||||
|
@ -50,4 +50,80 @@ describe('models.field', () => {
|
|||||||
{label: 'xx', value: 'xx'},
|
{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',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -238,6 +238,11 @@ export default {
|
|||||||
list: {
|
list: {
|
||||||
sort: 'sort',
|
sort: 'sort',
|
||||||
},
|
},
|
||||||
|
get: {
|
||||||
|
fields: {
|
||||||
|
appends: ['children'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
type: 'drawerSelect',
|
type: 'drawerSelect',
|
||||||
|
@ -90,6 +90,36 @@ export default {
|
|||||||
"target": "timeFormat",
|
"target": "timeFormat",
|
||||||
"condition": "{{ ['time'].indexOf($self.value) !== -1 }}"
|
"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",
|
"type": "value:visible",
|
||||||
"target": "component.showInForm",
|
"target": "component.showInForm",
|
||||||
@ -158,7 +188,6 @@ export default {
|
|||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
name: 'precision',
|
name: 'precision',
|
||||||
title: '精度',
|
title: '精度',
|
||||||
defaultValue: 1,
|
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{value: 1, label: '1'},
|
{value: 1, label: '1'},
|
||||||
{value: 0.1, label: '1.0'},
|
{value: 0.1, label: '1.0'},
|
||||||
@ -169,6 +198,7 @@ export default {
|
|||||||
component: {
|
component: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
|
default: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -176,7 +206,6 @@ export default {
|
|||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
name: 'dateFormat',
|
name: 'dateFormat',
|
||||||
title: '日期格式',
|
title: '日期格式',
|
||||||
defaultValue: 'YYYY/MM/DD',
|
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{value: 'YYYY/MM/DD', label: '年/月/日'},
|
{value: 'YYYY/MM/DD', label: '年/月/日'},
|
||||||
{value: 'YYYY-MM-DD', label: '年-月-日'},
|
{value: 'YYYY-MM-DD', label: '年-月-日'},
|
||||||
@ -185,6 +214,7 @@ export default {
|
|||||||
component: {
|
component: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
|
default: 'YYYY/MM/DD',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -192,10 +222,10 @@ export default {
|
|||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
name: 'showTime',
|
name: 'showTime',
|
||||||
title: '显示时间',
|
title: '显示时间',
|
||||||
defaultValue: false,
|
|
||||||
component: {
|
component: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
|
default: false,
|
||||||
"x-linkages": [
|
"x-linkages": [
|
||||||
{
|
{
|
||||||
"type": "value:visible",
|
"type": "value:visible",
|
||||||
@ -210,7 +240,6 @@ export default {
|
|||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
name: 'timeFormat',
|
name: 'timeFormat',
|
||||||
title: '时间格式',
|
title: '时间格式',
|
||||||
defaultValue: 'HH:mm:ss',
|
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{ value: 'HH:mm:ss', label: '24小时制' },
|
{ value: 'HH:mm:ss', label: '24小时制' },
|
||||||
{ value: 'hh:mm:ss a', label: '12小时制' },
|
{ value: 'hh:mm:ss a', label: '12小时制' },
|
||||||
@ -218,6 +247,7 @@ export default {
|
|||||||
component: {
|
component: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
|
default: 'HH:mm:ss',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -234,18 +264,80 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
interface: 'linkTo',
|
interface: 'string',
|
||||||
multiple: true,
|
type: 'virtual',
|
||||||
type: 'hasMany',
|
name: 'target',
|
||||||
name: 'children',
|
title: '要关联的数据表',
|
||||||
title: '子字段',
|
|
||||||
target: 'fields',
|
|
||||||
foreignKey: 'parent_id',
|
|
||||||
sourceKey: 'id',
|
|
||||||
component: {
|
component: {
|
||||||
type: 'drawerSelect',
|
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',
|
interface: 'textarea',
|
||||||
type: 'virtual',
|
type: 'virtual',
|
||||||
|
@ -55,7 +55,7 @@ export default {
|
|||||||
required: true,
|
required: true,
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{ label: '详情数据', value: 'details' },
|
{ label: '详情数据', value: 'details' },
|
||||||
{ label: '相关数据', value: 'association', disabled: true },
|
{ label: '相关数据', value: 'association' },
|
||||||
{ label: '模块组合', value: 'module', disabled: true },
|
{ label: '模块组合', value: 'module', disabled: true },
|
||||||
],
|
],
|
||||||
component: {
|
component: {
|
||||||
@ -78,7 +78,7 @@ export default {
|
|||||||
name: 'association',
|
name: 'association',
|
||||||
title: '相关数据表',
|
title: '相关数据表',
|
||||||
component: {
|
component: {
|
||||||
type: 'string',
|
type: 'drawerSelect',
|
||||||
showInDetail: true,
|
showInDetail: true,
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
},
|
},
|
||||||
|
@ -26,6 +26,7 @@ export default {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'title',
|
name: 'title',
|
||||||
title: '视图名称',
|
title: '视图名称',
|
||||||
|
required: true,
|
||||||
component: {
|
component: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
className: 'drag-visible',
|
className: 'drag-visible',
|
||||||
@ -51,9 +52,10 @@ export default {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'type',
|
name: 'type',
|
||||||
title: '视图类型',
|
title: '视图类型',
|
||||||
|
required: true,
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{ label: '表格', value: 'table' },
|
{ label: '表格', value: 'table' },
|
||||||
{ label: '表单', value: 'form' },
|
// { label: '表单', value: 'form' },
|
||||||
{ label: '看板', value: 'kanban', disabled: true },
|
{ label: '看板', value: 'kanban', disabled: true },
|
||||||
{ label: '日历', value: 'calendar', disabled: true },
|
{ label: '日历', value: 'calendar', disabled: true },
|
||||||
{ label: '地图', value: 'map', disabled: true },
|
{ label: '地图', value: 'map', disabled: true },
|
||||||
@ -63,6 +65,7 @@ export default {
|
|||||||
showInTable: true,
|
showInTable: true,
|
||||||
showInDetail: true,
|
showInDetail: true,
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
|
default: 'table',
|
||||||
"x-linkages": [
|
"x-linkages": [
|
||||||
{
|
{
|
||||||
"type": "value:visible",
|
"type": "value:visible",
|
||||||
@ -79,6 +82,7 @@ export default {
|
|||||||
title: '筛选数据',
|
title: '筛选数据',
|
||||||
developerMode: false,
|
developerMode: false,
|
||||||
mode: 'replace',
|
mode: 'replace',
|
||||||
|
defaultValue: {},
|
||||||
component: {
|
component: {
|
||||||
type: 'filter',
|
type: 'filter',
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
@ -89,13 +93,15 @@ export default {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'template',
|
name: 'template',
|
||||||
title: '模板',
|
title: '模板',
|
||||||
|
required: true,
|
||||||
dataSource: [
|
dataSource: [
|
||||||
{ label: '表单', value: 'DrawerForm' },
|
// { label: '表单', value: 'DrawerForm' },
|
||||||
{ label: '常规表格', value: 'Table' },
|
{ label: '常规表格', value: 'Table' },
|
||||||
{ label: '简易表格', value: 'SimpleTable' },
|
{ label: '简易表格', value: 'SimpleTable' },
|
||||||
],
|
],
|
||||||
component: {
|
component: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
default: 'Table',
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
showInDetail: true,
|
showInDetail: true,
|
||||||
showInForm: true,
|
showInForm: true,
|
||||||
|
@ -5,4 +5,11 @@ export default async function (model: FieldModel, options: any = {}) {
|
|||||||
if (migrate) {
|
if (migrate) {
|
||||||
await model.migrate(options);
|
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')}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,51 @@
|
|||||||
import FieldModel from '../models/field';
|
import FieldModel from '../models/field';
|
||||||
import * as types from '../interfaces/types';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export default async function (model: FieldModel) {
|
export default async function (model: FieldModel, options) {
|
||||||
// if (model.get('interface')) {
|
|
||||||
// model.setInterface(model.get('interface'));
|
|
||||||
// }
|
|
||||||
// 生成随机 name 要放最后
|
// 生成随机 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,13 +270,13 @@ export const time = {
|
|||||||
// });
|
// });
|
||||||
export const subTable = {
|
export const subTable = {
|
||||||
title: '子表格',
|
title: '子表格',
|
||||||
disabled: true,
|
// disabled: true,
|
||||||
options: {
|
options: {
|
||||||
interface: 'subTable',
|
interface: 'subTable',
|
||||||
type: 'hasMany',
|
type: 'hasMany',
|
||||||
// fields: [],
|
// fields: [],
|
||||||
component: {
|
component: {
|
||||||
type: 'table',
|
type: 'subTable',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -324,14 +324,14 @@ export const subTable = {
|
|||||||
|
|
||||||
export const linkTo = {
|
export const linkTo = {
|
||||||
title: '关联数据',
|
title: '关联数据',
|
||||||
disabled: true,
|
// disabled: true,
|
||||||
options: {
|
options: {
|
||||||
interface: 'linkTo',
|
interface: 'linkTo',
|
||||||
multiple: true, // 可能影响 type
|
multiple: true, // 可能影响 type
|
||||||
type: 'belongsToMany',
|
type: 'belongsToMany',
|
||||||
// name,
|
// name,
|
||||||
// target: '关联表', // 用户会输入
|
// target: '关联表', // 用户会输入
|
||||||
filterable: true,
|
filterable: false,
|
||||||
component: {
|
component: {
|
||||||
type: 'drawerSelect',
|
type: 'drawerSelect',
|
||||||
},
|
},
|
||||||
|
@ -61,9 +61,9 @@ export class CollectionModel extends BaseModel {
|
|||||||
const associationTableNames = [];
|
const associationTableNames = [];
|
||||||
for (const [key, association] of table.getAssociations()) {
|
for (const [key, association] of table.getAssociations()) {
|
||||||
// TODO:是否需要考虑重载的情况?(暂时是跳过处理)
|
// TODO:是否需要考虑重载的情况?(暂时是跳过处理)
|
||||||
if (!this.database.isDefined(association.options.target)) {
|
// if (!this.database.isDefined(association.options.target)) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
associationTableNames.push(association.options.target);
|
associationTableNames.push(association.options.target);
|
||||||
}
|
}
|
||||||
if (associationTableNames.length) {
|
if (associationTableNames.length) {
|
||||||
@ -131,7 +131,7 @@ export class CollectionModel extends BaseModel {
|
|||||||
data = _.cloneDeep(data);
|
data = _.cloneDeep(data);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const { update } = options;
|
const { update } = options;
|
||||||
let collection;
|
let collection: CollectionModel;
|
||||||
if (data.name) {
|
if (data.name) {
|
||||||
collection = await this.findOne({
|
collection = await this.findOne({
|
||||||
...options,
|
...options,
|
||||||
@ -159,6 +159,7 @@ export class CollectionModel extends BaseModel {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const Model = this.database.getModel(key);
|
const Model = this.database.getModel(key);
|
||||||
|
const ids = [];
|
||||||
for (const index in data[key]) {
|
for (const index in data[key]) {
|
||||||
let model;
|
let model;
|
||||||
const item = data[key][index];
|
const item = data[key][index];
|
||||||
@ -189,6 +190,14 @@ export class CollectionModel extends BaseModel {
|
|||||||
collection_name: collection.name,
|
collection_name: collection.name,
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
if (model) {
|
||||||
|
ids.push(model.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (collection.get('internal')) {
|
||||||
|
await collection.updateAssociations({
|
||||||
|
[key]: ids,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return collection;
|
return collection;
|
||||||
|
@ -4,6 +4,8 @@ import { FieldOptions } from '@nocobase/database';
|
|||||||
import * as types from '../interfaces/types';
|
import * as types from '../interfaces/types';
|
||||||
import { merge } from '../utils';
|
import { merge } from '../utils';
|
||||||
import { BuildOptions } from 'sequelize';
|
import { BuildOptions } from 'sequelize';
|
||||||
|
import { SaveOptions, Utils } from 'sequelize';
|
||||||
|
import { generateCollectionName } from './collection';
|
||||||
|
|
||||||
export function generateFieldName(title?: string): string {
|
export function generateFieldName(title?: string): string {
|
||||||
return `f_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`;
|
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];
|
let args = [options, data];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
data = merge(...args);
|
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
|
// @ts-ignore
|
||||||
super(data, options);
|
super(data, options);
|
||||||
// console.log(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generateName() {
|
generateName() {
|
||||||
|
@ -79,6 +79,10 @@ const transforms = {
|
|||||||
if (interfaceType === 'multipleSelect') {
|
if (interfaceType === 'multipleSelect') {
|
||||||
set(prop, 'x-component-props.mode', 'multiple');
|
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)) {
|
if (['radio', 'select', 'multipleSelect', 'checkboxes'].includes(interfaceType)) {
|
||||||
prop.enum = field.get('dataSource');
|
prop.enum = field.get('dataSource');
|
||||||
}
|
}
|
||||||
@ -94,9 +98,14 @@ const transforms = {
|
|||||||
if (!get(field.component, 'showInDetail')) {
|
if (!get(field.component, 'showInDetail')) {
|
||||||
continue;
|
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({
|
arr.push({
|
||||||
...field.toJSON(),
|
...field.toJSON(),
|
||||||
...field.options,
|
...props,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
|
Loading…
Reference in New Issue
Block a user