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