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,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'boolean',
 | 
			
		||||
      title: '是/否',
 | 
			
		||||
      component: {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'select',
 | 
			
		||||
      title: '下拉',
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        {value: 'value1', label: '选项1'},
 | 
			
		||||
        {value: 'value2', label: '选项2'},
 | 
			
		||||
        {value: 'value3', label: '选项3'},
 | 
			
		||||
      ],
 | 
			
		||||
      component: {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'multipleSelect',
 | 
			
		||||
      title: '下拉多选',
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        {value: 'value1', label: '选项1'},
 | 
			
		||||
        {value: 'value2', label: '选项2'},
 | 
			
		||||
        {value: 'value3', label: '选项3'},
 | 
			
		||||
      ],
 | 
			
		||||
      component: {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'radio',
 | 
			
		||||
      title: '单选框',
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        {value: 'value1', label: '选项1'},
 | 
			
		||||
        {value: 'value2', label: '选项2'},
 | 
			
		||||
        {value: 'value3', label: '选项3'},
 | 
			
		||||
      ],
 | 
			
		||||
      component: {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'checkboxes',
 | 
			
		||||
      title: '多选框',
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        {value: 'value1', label: '选项1'},
 | 
			
		||||
        {value: 'value2', label: '选项2'},
 | 
			
		||||
        {value: 'value3', label: '选项3'},
 | 
			
		||||
      ],
 | 
			
		||||
      component: {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'subTable',
 | 
			
		||||
      title: '子表格',
 | 
			
		||||
      children: [
 | 
			
		||||
        {
 | 
			
		||||
          interface: 'string',
 | 
			
		||||
          title: '标题',
 | 
			
		||||
          component: {
 | 
			
		||||
            showInTable: true,
 | 
			
		||||
            showInDetail: true,
 | 
			
		||||
            showInForm: true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          interface: 'textarea',
 | 
			
		||||
          title: '内容',
 | 
			
		||||
          component: {
 | 
			
		||||
            showInTable: true,
 | 
			
		||||
            showInDetail: true,
 | 
			
		||||
            showInForm: true,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      component: {
 | 
			
		||||
        // showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'datetime',
 | 
			
		||||
      title: '日期',
 | 
			
		||||
@ -78,6 +172,24 @@ export default {
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'createdBy',
 | 
			
		||||
      title: '创建人',
 | 
			
		||||
      component: {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        // showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'updatedBy',
 | 
			
		||||
      title: '更新人',
 | 
			
		||||
      component: {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        // showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'createdAt',
 | 
			
		||||
      title: '创建日期',
 | 
			
		||||
 | 
			
		||||
@ -6,9 +6,9 @@ import associated from '../../../actions/src/middlewares/associated';
 | 
			
		||||
import { Op } from 'sequelize';
 | 
			
		||||
 | 
			
		||||
const sync = {
 | 
			
		||||
  force: true,
 | 
			
		||||
  force: false,
 | 
			
		||||
  alter: {
 | 
			
		||||
    drop: true,
 | 
			
		||||
    drop: false,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -59,7 +59,7 @@ api.resourcer.use(async (ctx: actions.Context, next) => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
api.resourcer.use(async (ctx: actions.Context, next) => {
 | 
			
		||||
  const { resourceName } = ctx.action.params;
 | 
			
		||||
  const { resourceName, fields = {} } = ctx.action.params;
 | 
			
		||||
  const table = ctx.db.getTable(resourceName);
 | 
			
		||||
  // ctx.state.developerMode = {[Op.not]: null};
 | 
			
		||||
  ctx.state.developerMode = false;
 | 
			
		||||
@ -67,8 +67,8 @@ api.resourcer.use(async (ctx: actions.Context, next) => {
 | 
			
		||||
    ctx.action.setParam('filter.developerMode', ctx.state.developerMode);
 | 
			
		||||
  }
 | 
			
		||||
  if (table) {
 | 
			
		||||
    const except = [];
 | 
			
		||||
    const appends = [];
 | 
			
		||||
    const except = fields.except || [];
 | 
			
		||||
    const appends = fields.appends || [];
 | 
			
		||||
    for (const [name, field] of table.getFields()) {
 | 
			
		||||
      if (field.options.hidden) {
 | 
			
		||||
        except.push(field.options.name);
 | 
			
		||||
 | 
			
		||||
@ -173,17 +173,19 @@ api.registerPlugin('plugin-users', [path.resolve(__dirname, '../../../plugin-use
 | 
			
		||||
    await Collection.import(table.getOptions(), { update: true, migrate: false });
 | 
			
		||||
  }
 | 
			
		||||
  await Page.import(data);
 | 
			
		||||
  await User.findOrCreate({
 | 
			
		||||
  const user = await User.findOne({
 | 
			
		||||
    where: {
 | 
			
		||||
      username: "admin",
 | 
			
		||||
    },
 | 
			
		||||
    defaults: {
 | 
			
		||||
  });
 | 
			
		||||
  if (!user) {
 | 
			
		||||
    await User.create({
 | 
			
		||||
      nickname: "超级管理员",
 | 
			
		||||
      password: "admin",
 | 
			
		||||
      username: "admin",
 | 
			
		||||
      token: "38979f07e1fca68fb3d2",
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  await database.getModel('collections').import(require('./collections/example').default);
 | 
			
		||||
  await database.close();
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
@ -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 { Upload } from './upload'
 | 
			
		||||
import { Filter } from './filter'
 | 
			
		||||
import { DrawerSelect } from './drawer-select'
 | 
			
		||||
import { SubTable } from './sub-table'
 | 
			
		||||
 | 
			
		||||
export const setup = () => {
 | 
			
		||||
  registerFormFields({
 | 
			
		||||
@ -43,5 +45,7 @@ export const setup = () => {
 | 
			
		||||
    rating: Rating,
 | 
			
		||||
    upload: Upload,
 | 
			
		||||
    filter: Filter,
 | 
			
		||||
    drawerSelect: DrawerSelect,
 | 
			
		||||
    subTable: SubTable,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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,
 | 
			
		||||
    resourceKey,
 | 
			
		||||
  } = props;
 | 
			
		||||
  const { actions = [], fields = [] } = props.schema;
 | 
			
		||||
  const { data = {}, loading, refresh } = useRequest(() => {
 | 
			
		||||
    const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
 | 
			
		||||
    return api.resource(name).get({
 | 
			
		||||
      resourceKey,
 | 
			
		||||
      associatedKey,
 | 
			
		||||
      'fields[appends]': fields.filter(field => get(field, 'interface') === 'subTable').map(field => field.name).join(',')
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  console.log(props);
 | 
			
		||||
  const { actions = [], fields = [] } = props.schema;
 | 
			
		||||
  return (
 | 
			
		||||
    <Card bordered={false}>
 | 
			
		||||
      <Actions
 | 
			
		||||
@ -40,7 +41,7 @@ export function Details(props: any) {
 | 
			
		||||
        <Descriptions bordered column={1}>
 | 
			
		||||
          {fields.map((field: any) => {
 | 
			
		||||
            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)}/>
 | 
			
		||||
              </Descriptions.Item>
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,11 @@
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import moment from 'moment';
 | 
			
		||||
import { Tag, Popover } from 'antd';
 | 
			
		||||
import { Tag, Popover, Table } from 'antd';
 | 
			
		||||
import Icon from '@/components/icons';
 | 
			
		||||
import get from 'lodash/get';
 | 
			
		||||
import isEmpty from 'lodash/isEmpty';
 | 
			
		||||
import { fields2columns } from '../SortableTable';
 | 
			
		||||
 | 
			
		||||
const InterfaceTypes = new Map<string, any>();
 | 
			
		||||
 | 
			
		||||
@ -27,6 +28,12 @@ function getFieldComponent(type) {
 | 
			
		||||
 | 
			
		||||
export function StringField(props: any) {
 | 
			
		||||
  const { value } = props;
 | 
			
		||||
  if (!value) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  if (typeof value === 'object') {
 | 
			
		||||
    return JSON.stringify(value);
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <>{value}</>
 | 
			
		||||
  );
 | 
			
		||||
@ -157,6 +164,19 @@ export function RealtionField(props: any) {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function SubTableField(props: any) {
 | 
			
		||||
  const { schema: { children }, value } = props;
 | 
			
		||||
  console.log(value);
 | 
			
		||||
  if (!Array.isArray(value)) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <Table columns={fields2columns(children)} dataSource={value} pagination={false}/>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
registerFieldComponents({
 | 
			
		||||
  string: StringField,
 | 
			
		||||
  textarea: TextareaField,
 | 
			
		||||
@ -173,6 +193,7 @@ registerFieldComponents({
 | 
			
		||||
  icon: IconField,
 | 
			
		||||
  createdBy: RealtionField,
 | 
			
		||||
  updatedBy: RealtionField,
 | 
			
		||||
  subTable: SubTableField,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default function Field(props: any) {
 | 
			
		||||
 | 
			
		||||
@ -59,9 +59,7 @@ export async function getApp() {
 | 
			
		||||
  app.resourcer.registerActionHandlers({...actions.associate, ...actions.common});
 | 
			
		||||
  app.registerPlugin('collections', [plugin]);
 | 
			
		||||
  await app.loadPlugins();
 | 
			
		||||
  await app.database.sync({
 | 
			
		||||
    force: true,
 | 
			
		||||
  });
 | 
			
		||||
  await app.database.sync();
 | 
			
		||||
  // 表配置信息存到数据库里
 | 
			
		||||
  // const tables = app.database.getTables([]);
 | 
			
		||||
  // for (const table of tables) {
 | 
			
		||||
 | 
			
		||||
@ -50,4 +50,80 @@ describe('models.field', () => {
 | 
			
		||||
      {label: 'xx', value: 'xx'},
 | 
			
		||||
    ]);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it.only('sub table field', async () => {
 | 
			
		||||
    const [Collection, Field] = app.database.getModels(['collections', 'fields']);
 | 
			
		||||
    const options = {
 | 
			
		||||
      title: 'tests',
 | 
			
		||||
      name: 'tests',
 | 
			
		||||
      fields: [
 | 
			
		||||
        {
 | 
			
		||||
          interface: 'subTable',
 | 
			
		||||
          title: '子表格',
 | 
			
		||||
          name: 'subs',
 | 
			
		||||
          children: [
 | 
			
		||||
            {
 | 
			
		||||
              interface: 'string',
 | 
			
		||||
              title: '名称',
 | 
			
		||||
              name: 'name',
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    };
 | 
			
		||||
    const collection = await Collection.create(options);
 | 
			
		||||
    await collection.updateAssociations(options);
 | 
			
		||||
    const field = await Field.findOne({
 | 
			
		||||
      where: {
 | 
			
		||||
        title: '子表格',
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    await field.createChild({
 | 
			
		||||
      interface: 'string',
 | 
			
		||||
      title: '名称',
 | 
			
		||||
      name: 'title',
 | 
			
		||||
    });
 | 
			
		||||
    const Test = app.database.getModel('tests');
 | 
			
		||||
    const Sub = app.database.getModel('subs');
 | 
			
		||||
    // console.log(Test.associations);
 | 
			
		||||
    // console.log(Sub.rawAttributes);
 | 
			
		||||
    const test = await Test.create({});
 | 
			
		||||
    const sub = await test.createSub({name: 'name1', title: 'title1'});
 | 
			
		||||
    expect(sub.toJSON()).toMatchObject({name: 'name1', title: 'title1'})
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('sub table field', async () => {
 | 
			
		||||
    const [Collection, Field] = app.database.getModels(['collections', 'fields']);
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    const options = {
 | 
			
		||||
      title: 'tests',
 | 
			
		||||
      name: 'tests',
 | 
			
		||||
      fields: [
 | 
			
		||||
        {
 | 
			
		||||
          interface: 'subTable',
 | 
			
		||||
          title: '子表格',
 | 
			
		||||
          // name: 'subs',
 | 
			
		||||
          children: [
 | 
			
		||||
            {
 | 
			
		||||
              interface: 'string',
 | 
			
		||||
              title: '名称',
 | 
			
		||||
              // name: 'name',
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    };
 | 
			
		||||
    const collection = await Collection.create(options);
 | 
			
		||||
    await collection.updateAssociations(options);
 | 
			
		||||
    const field = await Field.findOne({
 | 
			
		||||
      where: {
 | 
			
		||||
        title: '子表格',
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    await field.createChild({
 | 
			
		||||
      interface: 'string',
 | 
			
		||||
      title: '名称',
 | 
			
		||||
      name: 'title',
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -238,6 +238,11 @@ export default {
 | 
			
		||||
        list: {
 | 
			
		||||
          sort: 'sort',
 | 
			
		||||
        },
 | 
			
		||||
        get: {
 | 
			
		||||
          fields: {
 | 
			
		||||
            appends: ['children'],
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'drawerSelect',
 | 
			
		||||
 | 
			
		||||
@ -90,6 +90,36 @@ export default {
 | 
			
		||||
            "target": "timeFormat",
 | 
			
		||||
            "condition": "{{ ['time'].indexOf($self.value) !== -1 }}"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
            "target": "multiple",
 | 
			
		||||
            "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
            "target": "target",
 | 
			
		||||
            "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
            "target": "labelField",
 | 
			
		||||
            "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
            "target": "createable",
 | 
			
		||||
            "condition": "{{ ['linkTo'].indexOf($self.value) !== -1 }}"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
            "target": "children",
 | 
			
		||||
            "condition": "{{ ['subTable'].indexOf($self.value) !== -1 }}"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
            "target": "component.showInTable",
 | 
			
		||||
            "condition": "{{ ['subTable'].indexOf($self.value) === -1 }}"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
            "target": "component.showInForm",
 | 
			
		||||
@ -158,7 +188,6 @@ export default {
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'precision',
 | 
			
		||||
      title: '精度',
 | 
			
		||||
      defaultValue: 1,
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        {value: 1, label: '1'},
 | 
			
		||||
        {value: 0.1, label: '1.0'},
 | 
			
		||||
@ -169,6 +198,7 @@ export default {
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'number',
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
        default: 1,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -176,7 +206,6 @@ export default {
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'dateFormat',
 | 
			
		||||
      title: '日期格式',
 | 
			
		||||
      defaultValue: 'YYYY/MM/DD',
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        {value: 'YYYY/MM/DD', label: '年/月/日'},
 | 
			
		||||
        {value: 'YYYY-MM-DD', label: '年-月-日'},
 | 
			
		||||
@ -185,6 +214,7 @@ export default {
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
        default: 'YYYY/MM/DD',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -192,10 +222,10 @@ export default {
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'showTime',
 | 
			
		||||
      title: '显示时间',
 | 
			
		||||
      defaultValue: false,
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'boolean',
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
        default: false,
 | 
			
		||||
        "x-linkages": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
@ -210,7 +240,6 @@ export default {
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'timeFormat',
 | 
			
		||||
      title: '时间格式',
 | 
			
		||||
      defaultValue: 'HH:mm:ss',
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        { value: 'HH:mm:ss', label: '24小时制' },
 | 
			
		||||
        { value: 'hh:mm:ss a', label: '12小时制' },
 | 
			
		||||
@ -218,6 +247,7 @@ export default {
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
        default: 'HH:mm:ss',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
@ -234,18 +264,80 @@ export default {
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'linkTo',
 | 
			
		||||
      multiple: true,
 | 
			
		||||
      type: 'hasMany',
 | 
			
		||||
      name: 'children',
 | 
			
		||||
      title: '子字段',
 | 
			
		||||
      target: 'fields',
 | 
			
		||||
      foreignKey: 'parent_id',
 | 
			
		||||
      sourceKey: 'id',
 | 
			
		||||
      interface: 'string',
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'target',
 | 
			
		||||
      title: '要关联的数据表',
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'drawerSelect',
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'string',
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'labelField',
 | 
			
		||||
      title: '要关联的字段',
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'drawerSelect',
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'boolean',
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'multiple',
 | 
			
		||||
      title: '允许添加多条记录',
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'checkbox',
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
        default: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'boolean',
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
      name: 'createable',
 | 
			
		||||
      title: '允许直接在关联的数据表内新建数据',
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'checkbox',
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'subTable',
 | 
			
		||||
      type: 'hasMany',
 | 
			
		||||
      name: 'children',
 | 
			
		||||
      target: 'fields',
 | 
			
		||||
      sourceKey: 'id',
 | 
			
		||||
      foreignKey: 'parent_id',
 | 
			
		||||
      title: '子表格字段',
 | 
			
		||||
      // visible: true,
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'subTable',
 | 
			
		||||
        default: [],
 | 
			
		||||
        // showInTable: true,
 | 
			
		||||
        // showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    // {
 | 
			
		||||
    //   interface: 'linkTo',
 | 
			
		||||
    //   multiple: true,
 | 
			
		||||
    //   type: 'hasMany',
 | 
			
		||||
    //   name: 'children',
 | 
			
		||||
    //   title: '子字段',
 | 
			
		||||
    //   target: 'fields',
 | 
			
		||||
    //   foreignKey: 'parent_id',
 | 
			
		||||
    //   sourceKey: 'id',
 | 
			
		||||
    //   component: {
 | 
			
		||||
    //     type: 'drawerSelect',
 | 
			
		||||
    //   },
 | 
			
		||||
    // },
 | 
			
		||||
    {
 | 
			
		||||
      interface: 'textarea',
 | 
			
		||||
      type: 'virtual',
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,7 @@ export default {
 | 
			
		||||
      required: true,
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        { label: '详情数据', value: 'details' },
 | 
			
		||||
        { label: '相关数据', value: 'association', disabled: true },
 | 
			
		||||
        { label: '相关数据', value: 'association' },
 | 
			
		||||
        { label: '模块组合', value: 'module', disabled: true },
 | 
			
		||||
      ],
 | 
			
		||||
      component: {
 | 
			
		||||
@ -78,7 +78,7 @@ export default {
 | 
			
		||||
      name: 'association',
 | 
			
		||||
      title: '相关数据表',
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        type: 'drawerSelect',
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ export default {
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'title',
 | 
			
		||||
      title: '视图名称',
 | 
			
		||||
      required: true,
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        className: 'drag-visible',
 | 
			
		||||
@ -51,9 +52,10 @@ export default {
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'type',
 | 
			
		||||
      title: '视图类型',
 | 
			
		||||
      required: true,
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        { label: '表格', value: 'table' },
 | 
			
		||||
        { label: '表单', value: 'form' },
 | 
			
		||||
        // { label: '表单', value: 'form' },
 | 
			
		||||
        { label: '看板', value: 'kanban', disabled: true },
 | 
			
		||||
        { label: '日历', value: 'calendar', disabled: true },
 | 
			
		||||
        { label: '地图', value: 'map', disabled: true },
 | 
			
		||||
@ -63,6 +65,7 @@ export default {
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
        default: 'table',
 | 
			
		||||
        "x-linkages": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "value:visible",
 | 
			
		||||
@ -79,6 +82,7 @@ export default {
 | 
			
		||||
      title: '筛选数据',
 | 
			
		||||
      developerMode: false,
 | 
			
		||||
      mode: 'replace',
 | 
			
		||||
      defaultValue: {},
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'filter',
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
@ -89,13 +93,15 @@ export default {
 | 
			
		||||
      type: 'string',
 | 
			
		||||
      name: 'template',
 | 
			
		||||
      title: '模板',
 | 
			
		||||
      required: true,
 | 
			
		||||
      dataSource: [
 | 
			
		||||
        { label: '表单', value: 'DrawerForm' },
 | 
			
		||||
        // { label: '表单', value: 'DrawerForm' },
 | 
			
		||||
        { label: '常规表格', value: 'Table' },
 | 
			
		||||
        { label: '简易表格', value: 'SimpleTable' },
 | 
			
		||||
      ],
 | 
			
		||||
      component: {
 | 
			
		||||
        type: 'string',
 | 
			
		||||
        default: 'Table',
 | 
			
		||||
        showInTable: true,
 | 
			
		||||
        showInDetail: true,
 | 
			
		||||
        showInForm: true,
 | 
			
		||||
 | 
			
		||||
@ -5,4 +5,11 @@ export default async function (model: FieldModel, options: any = {}) {
 | 
			
		||||
  if (migrate) {
 | 
			
		||||
    await model.migrate(options);
 | 
			
		||||
  }
 | 
			
		||||
  if (model.get('collection_name') && model.get('parent_id')) {
 | 
			
		||||
    const parent = await model.getParent({
 | 
			
		||||
      ...options,
 | 
			
		||||
    });
 | 
			
		||||
    const Collection = model.database.getModel('collections');
 | 
			
		||||
    await Collection.load({...options, where: {name: parent.get('collection_name')}});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,51 @@
 | 
			
		||||
import FieldModel from '../models/field';
 | 
			
		||||
import * as types from '../interfaces/types';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
export default async function (model: FieldModel) {
 | 
			
		||||
  // if (model.get('interface')) {
 | 
			
		||||
  //   model.setInterface(model.get('interface'));
 | 
			
		||||
  // }
 | 
			
		||||
export default async function (model: FieldModel, options) {
 | 
			
		||||
  // 生成随机 name 要放最后
 | 
			
		||||
  model.generateNameIfNull();
 | 
			
		||||
  // model.generateNameIfNull();
 | 
			
		||||
  const Collection = model.database.getModel('collections');
 | 
			
		||||
  if (model.get('interface') === 'subTable') {
 | 
			
		||||
    const target = model.get('target');
 | 
			
		||||
    if (target) {
 | 
			
		||||
      const collection = await Collection.findOne({
 | 
			
		||||
        ...options,
 | 
			
		||||
        where: {
 | 
			
		||||
          name: target,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      if (!collection) {
 | 
			
		||||
        await Collection.create({
 | 
			
		||||
          name: target,
 | 
			
		||||
          internal: true,
 | 
			
		||||
          developerMode: true,
 | 
			
		||||
        }, options);
 | 
			
		||||
      }
 | 
			
		||||
      await Collection.load({...options, where: {name: model.get('name')}})
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // 如果 collection_name 不存在
 | 
			
		||||
  if (!model.get('collection_name') && model.get('parent_id')) {
 | 
			
		||||
    const parent = await model.getParent({
 | 
			
		||||
      ...options,
 | 
			
		||||
    });
 | 
			
		||||
    const target = parent.get('target');
 | 
			
		||||
    if (target) {
 | 
			
		||||
      const collection = await Collection.findOne({
 | 
			
		||||
        ...options,
 | 
			
		||||
        where: {
 | 
			
		||||
          name: target,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
      if (!collection) {
 | 
			
		||||
        await Collection.create({
 | 
			
		||||
          name: target,
 | 
			
		||||
          internal: true,
 | 
			
		||||
          developerMode: true,
 | 
			
		||||
        }, options);
 | 
			
		||||
      }
 | 
			
		||||
      await Collection.load({...options, where: {name: parent.get('name')}})
 | 
			
		||||
      model.set('collection_name', target);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -270,13 +270,13 @@ export const time = {
 | 
			
		||||
// });
 | 
			
		||||
export const subTable = {
 | 
			
		||||
  title: '子表格',
 | 
			
		||||
  disabled: true,
 | 
			
		||||
  // disabled: true,
 | 
			
		||||
  options: {
 | 
			
		||||
    interface: 'subTable',
 | 
			
		||||
    type: 'hasMany',
 | 
			
		||||
    // fields: [],
 | 
			
		||||
    component: {
 | 
			
		||||
      type: 'table',
 | 
			
		||||
      type: 'subTable',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@ -324,14 +324,14 @@ export const subTable = {
 | 
			
		||||
 | 
			
		||||
export const linkTo = {
 | 
			
		||||
  title: '关联数据',
 | 
			
		||||
  disabled: true,
 | 
			
		||||
  // disabled: true,
 | 
			
		||||
  options: {
 | 
			
		||||
    interface: 'linkTo',
 | 
			
		||||
    multiple: true, // 可能影响 type
 | 
			
		||||
    type: 'belongsToMany',
 | 
			
		||||
    // name,
 | 
			
		||||
    // target: '关联表', // 用户会输入
 | 
			
		||||
    filterable: true,
 | 
			
		||||
    filterable: false,
 | 
			
		||||
    component: {
 | 
			
		||||
      type: 'drawerSelect',
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -61,9 +61,9 @@ export class CollectionModel extends BaseModel {
 | 
			
		||||
    const associationTableNames = [];
 | 
			
		||||
    for (const [key, association] of table.getAssociations()) {
 | 
			
		||||
      // TODO:是否需要考虑重载的情况?(暂时是跳过处理)
 | 
			
		||||
      if (!this.database.isDefined(association.options.target)) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      // if (!this.database.isDefined(association.options.target)) {
 | 
			
		||||
      //   continue;
 | 
			
		||||
      // }
 | 
			
		||||
      associationTableNames.push(association.options.target);
 | 
			
		||||
    }
 | 
			
		||||
    if (associationTableNames.length) {
 | 
			
		||||
@ -131,7 +131,7 @@ export class CollectionModel extends BaseModel {
 | 
			
		||||
    data = _.cloneDeep(data);
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    const { update } = options;
 | 
			
		||||
    let collection;
 | 
			
		||||
    let collection: CollectionModel;
 | 
			
		||||
    if (data.name) {
 | 
			
		||||
      collection = await this.findOne({
 | 
			
		||||
        ...options,
 | 
			
		||||
@ -159,6 +159,7 @@ export class CollectionModel extends BaseModel {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const Model = this.database.getModel(key);
 | 
			
		||||
      const ids = [];
 | 
			
		||||
      for (const index in data[key]) {
 | 
			
		||||
        let model;
 | 
			
		||||
        const item = data[key][index];
 | 
			
		||||
@ -189,6 +190,14 @@ export class CollectionModel extends BaseModel {
 | 
			
		||||
            collection_name: collection.name,
 | 
			
		||||
          }, options);
 | 
			
		||||
        }
 | 
			
		||||
        if (model) {
 | 
			
		||||
          ids.push(model.id);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (collection.get('internal')) {
 | 
			
		||||
        await collection.updateAssociations({
 | 
			
		||||
          [key]: ids,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return collection;
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@ import { FieldOptions } from '@nocobase/database';
 | 
			
		||||
import * as types from '../interfaces/types';
 | 
			
		||||
import { merge } from '../utils';
 | 
			
		||||
import { BuildOptions } from 'sequelize';
 | 
			
		||||
import { SaveOptions, Utils } from 'sequelize';
 | 
			
		||||
import { generateCollectionName } from './collection';
 | 
			
		||||
 | 
			
		||||
export function generateFieldName(title?: string): string {
 | 
			
		||||
  return `f_${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`;
 | 
			
		||||
@ -23,10 +25,21 @@ export class FieldModel extends BaseModel {
 | 
			
		||||
      let args = [options, data];
 | 
			
		||||
      // @ts-ignore
 | 
			
		||||
      data = merge(...args);
 | 
			
		||||
      if (['hasOne', 'hasMany', 'belongsTo', 'belongsToMany'].includes(data.type)) {
 | 
			
		||||
        if (!data.name) {
 | 
			
		||||
          data.name = generateFieldName();
 | 
			
		||||
          data.target  = generateCollectionName();
 | 
			
		||||
        }
 | 
			
		||||
        if (!data.target) {
 | 
			
		||||
          data.target = ['hasOne', 'belongsTo'].includes(data.type) ? Utils.pluralize(data.name) : data.name;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (!data.name) {
 | 
			
		||||
        data.name = generateFieldName();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    super(data, options);
 | 
			
		||||
    // console.log(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generateName() {
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,10 @@ const transforms = {
 | 
			
		||||
      if (interfaceType === 'multipleSelect') {
 | 
			
		||||
        set(prop, 'x-component-props.mode', 'multiple');
 | 
			
		||||
      }
 | 
			
		||||
      if (interfaceType === 'subTable' && field.get('target')) {
 | 
			
		||||
        set(prop, 'x-component-props.target', field.get('target'));
 | 
			
		||||
        // resourceName
 | 
			
		||||
      }
 | 
			
		||||
      if (['radio', 'select', 'multipleSelect', 'checkboxes'].includes(interfaceType)) {
 | 
			
		||||
        prop.enum = field.get('dataSource');
 | 
			
		||||
      }
 | 
			
		||||
@ -94,9 +98,14 @@ const transforms = {
 | 
			
		||||
      if (!get(field.component, 'showInDetail')) {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      const props = {};
 | 
			
		||||
      if (field.get('interface') === 'subTable') {
 | 
			
		||||
        const children = await field.getChildren();
 | 
			
		||||
        props['children'] = children.map(child => ({...child.toJSON(), dataIndex: child.name.split('.')}))
 | 
			
		||||
      }
 | 
			
		||||
      arr.push({
 | 
			
		||||
        ...field.toJSON(),
 | 
			
		||||
        ...field.options,
 | 
			
		||||
        ...props,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    return arr;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user