This commit is contained in:
chenos 2021-07-27 10:34:52 +08:00
parent 06fbd8c51b
commit 8bfb5cf85c
39 changed files with 656 additions and 391 deletions

View File

@ -23,7 +23,7 @@ jobs:
- run: npm install - run: npm install
- run: npm run bootstrap - run: npm run bootstrap
- run: echo "API_URL=${{secrets.API_URL}}" >> .env - run: echo "API_URL=${{ secrets.API_URL }}" >> .env
- run: npm run build-docs - run: npm run build-docs
- name: Deploy - name: Deploy

View File

@ -43,7 +43,7 @@ const plugins = [
'@nocobase/plugin-ui-schema', '@nocobase/plugin-ui-schema',
// '@nocobase/plugin-action-logs', // '@nocobase/plugin-action-logs',
// '@nocobase/plugin-pages', // '@nocobase/plugin-pages',
// '@nocobase/plugin-users', '@nocobase/plugin-users',
// '@nocobase/plugin-file-manager', // '@nocobase/plugin-file-manager',
// '@nocobase/plugin-permissions', // '@nocobase/plugin-permissions',
// '@nocobase/plugin-automations', // '@nocobase/plugin-automations',

View File

@ -17,6 +17,11 @@ import * as uiSchema from './ui-schema';
// tables: ['collections', 'fields', 'actions', 'views', 'tabs'], // tables: ['collections', 'fields', 'actions', 'views', 'tabs'],
}); });
const config = require('@nocobase/plugin-users/src/collections/users').default;
const Collection = database.getModel('collections');
const collection = await Collection.create(config);
await collection.updateAssociations(config);
const Route = database.getModel('routes'); const Route = database.getModel('routes');
const data = [ const data = [

0
packages/app/src/app.ts Normal file
View File

View File

@ -1,10 +1,8 @@
import 'antd/dist/antd.css' import 'antd/dist/antd.css';
import { useRequest } from 'ahooks'; import { useRequest } from 'ahooks';
import { Spin } from 'antd'; import { Spin } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { import { MemoryRouter as Router } from 'react-router-dom';
MemoryRouter as Router,
} from 'react-router-dom';
import { import {
createRouteSwitch, createRouteSwitch,
AdminLayout, AdminLayout,
@ -19,8 +17,6 @@ const request = extend({
timeout: 1000, timeout: 1000,
}); });
// console.log = () => {}
const RouteSwitch = createRouteSwitch({ const RouteSwitch = createRouteSwitch({
components: { components: {
AdminLayout, AdminLayout,
@ -35,7 +31,7 @@ const App = () => {
}); });
if (loading) { if (loading) {
return <Spin/> return <Spin />;
} }
return ( return (

View File

@ -1,8 +1,29 @@
import { SchemaRenderer } from '../../'; import { SchemaRenderer } from '../../';
import React from 'react'; import React from 'react';
import { FormItem } from '@formily/antd'; import { FormItem } from '@formily/antd';
import { useCollectionContext } from '../../schemas';
import { action } from '@formily/reactive';
const useAsyncDataSource = (service: any) => (field: any) => {
field.loading = true;
service(field).then(
action((data: any) => {
field.dataSource = data;
field.loading = false;
}),
);
};
export default () => { export default () => {
const { data, loading } = useCollectionContext();
const loadCollections = async (field: any) => {
return data.map((item: any) => ({
label: item.title,
value: item.name,
}));
};
const schema = { const schema = {
type: 'array', type: 'array',
name: 'collections', name: 'collections',
@ -33,5 +54,11 @@ export default () => {
}, },
}, },
}; };
return <SchemaRenderer components={{ FormItem }} schema={schema} />; return (
<SchemaRenderer
scope={{ loadCollections, useAsyncDataSource }}
components={{ FormItem }}
schema={schema}
/>
);
}; };

View File

@ -29,7 +29,7 @@ import { useRequest } from 'ahooks';
import './style.less'; import './style.less';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { ISchema } from '@formily/react'; import { ISchema, Schema } from '@formily/react';
import Database from './datatable'; import Database from './datatable';
import { HighlightOutlined } from '@ant-design/icons'; import { HighlightOutlined } from '@ant-design/icons';
import { useCookieState } from 'ahooks'; import { useCookieState } from 'ahooks';
@ -51,14 +51,20 @@ function DesignableToggle() {
); );
} }
function LayoutWithMenu({ schema }) { interface LayoutWithMenuProps {
schema: Schema;
[key: string]: any;
}
function LayoutWithMenu(props: LayoutWithMenuProps) {
const { schema, defaultSelectedKeys } = props;
const match = useRouteMatch<any>(); const match = useRouteMatch<any>();
const location = useLocation(); const location = useLocation();
const sideMenuRef = useRef(); const sideMenuRef = useRef();
const history = useHistory();
const [activeKey, setActiveKey] = useState(match.params.name); const [activeKey, setActiveKey] = useState(match.params.name);
const [, setPageTitle] = usePageTitleContext(); const [, setPageTitle] = usePageTitleContext();
const onSelect = (info) => { const onSelect = (info) => {
console.log('LayoutWithMenu', info);
if (!info.schema) { if (!info.schema) {
setActiveKey(null); setActiveKey(null);
} else if (info.schema['x-component'] === 'Menu.SubMenu') { } else if (info.schema['x-component'] === 'Menu.SubMenu') {
@ -66,12 +72,13 @@ function LayoutWithMenu({ schema }) {
setActiveKey(null); setActiveKey(null);
} else { } else {
setActiveKey(info.schema.key); setActiveKey(info.schema.key);
history.push(`/admin/${info.schema.key}`);
if (info.schema.title) { if (info.schema.title) {
setPageTitle(info.schema.title); setPageTitle(info.schema.title);
} }
} }
}; };
console.log({ match });
return ( return (
<Layout> <Layout>
<Layout.Header style={{ display: 'flex' }}> <Layout.Header style={{ display: 'flex' }}>
@ -80,7 +87,7 @@ function LayoutWithMenu({ schema }) {
scope={{ scope={{
sideMenuRef, sideMenuRef,
onSelect, onSelect,
selectedKeys: [activeKey].filter(Boolean), selectedKeys: defaultSelectedKeys.filter(Boolean),
}} }}
/> />
<Database /> <Database />
@ -124,16 +131,42 @@ export function AdminLayout({ route }: any) {
formatResult: (result) => result?.data, formatResult: (result) => result?.data,
}, },
); );
const match = useRouteMatch<any>();
if (loading) { if (loading) {
return <Spin />; return <Spin />;
} }
const findProperties = (schema: Schema): Schema[] => {
if (!schema) {
return [];
}
return schema.reduceProperties((items, current) => {
if (current['key'] == match.params.name) {
return [...items, current];
}
return [...items, ...findProperties(current)];
}, []);
}
const current = findProperties(new Schema(data)).shift();
const defaultSelectedKeys = [current?.name];
let parent = current?.parent;
while(parent) {
if (parent['x-component'] === 'Menu') {
break;
}
defaultSelectedKeys.unshift(parent.name);
parent = parent.parent;
}
console.log('current?.title', current, current?.title, defaultSelectedKeys);
return ( return (
<SwithDesignableContextProvider> <SwithDesignableContextProvider>
<CollectionContextProvider> <CollectionContextProvider>
<PageTitleContextProvider> {/* @ts-ignore */}
<LayoutWithMenu schema={data} /> <PageTitleContextProvider defaultPageTitle={current?.title}>
<LayoutWithMenu defaultSelectedKeys={defaultSelectedKeys} current={current} schema={data} />
</PageTitleContextProvider> </PageTitleContextProvider>
</CollectionContextProvider> </CollectionContextProvider>
</SwithDesignableContextProvider> </SwithDesignableContextProvider>

View File

@ -51,7 +51,7 @@ import { CardItem } from '../../schemas/card-item';
import { DragAndDrop } from '../../schemas/drag-and-drop'; import { DragAndDrop } from '../../schemas/drag-and-drop';
import { TreeSelect } from '../../schemas/tree-select'; import { TreeSelect } from '../../schemas/tree-select';
import { Page } from '../../schemas/page'; import { Page } from '../../schemas/page';
import { Chart } from '../../schemas/chart'; // import { Chart } from '../../schemas/chart';
import { useCollectionContext, useSwithDesignableContext } from '../../schemas'; import { useCollectionContext, useSwithDesignableContext } from '../../schemas';
export const BlockContext = createContext({ dragRef: null }); export const BlockContext = createContext({ dragRef: null });
@ -72,7 +72,7 @@ export const SchemaField = createSchemaField({
Div, Div,
Space, Space,
Page, Page,
Chart, // Chart,
ArrayCollapse, ArrayCollapse,
ArrayTable, ArrayTable,

View File

@ -882,8 +882,9 @@ AddNew.PaneItem = observer((props: any) => {
placement={'bottomCenter'} placement={'bottomCenter'}
overlay={ overlay={
<Menu> <Menu>
<Menu.SubMenu title={'新建卡片'}> <Menu.ItemGroup title={'数据区块'}>
<Menu.Item <Menu.Item
icon={<IconPicker type={'FileOutlined'} />}
onClick={async () => { onClick={async () => {
let data: ISchema = { let data: ISchema = {
type: 'void', type: 'void',
@ -927,9 +928,10 @@ AddNew.PaneItem = observer((props: any) => {
}} }}
style={{ minWidth: 150 }} style={{ minWidth: 150 }}
> >
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
icon={<IconPicker type={'FormOutlined'} />}
onClick={async () => { onClick={async () => {
let data: ISchema = { let data: ISchema = {
type: 'void', type: 'void',
@ -972,47 +974,50 @@ AddNew.PaneItem = observer((props: any) => {
setVisible(false); setVisible(false);
}} }}
> >
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.ItemGroup>
<Menu.SubMenu title={'要展示的相关数据'}> <Menu.ItemGroup title={'相关数据区块'}>
<Menu.Item style={{ minWidth: 150 }}></Menu.Item> <Menu.Item style={{ minWidth: 150 }} icon={<IconPicker type={'HistoryOutlined'} />}></Menu.Item>
<Menu.Item></Menu.Item> <Menu.Item icon={<IconPicker type={'CommentOutlined'} />}></Menu.Item>
</Menu.SubMenu> </Menu.ItemGroup>
<Menu.Item <Menu.ItemGroup title={'多媒体区块'}>
onClick={async () => { <Menu.Item
let data: ISchema = { icon={<IconPicker type={'FileMarkdownOutlined'} />}
key: uid(), onClick={async () => {
type: 'void', let data: ISchema = {
default: '这是一段演示文字,**支持使用 Markdown 语法**', key: uid(),
'x-designable-bar': 'Markdown.Void.DesignableBar', type: 'void',
'x-decorator': 'CardItem', default: '这是一段演示文字,**支持使用 Markdown 语法**',
'x-read-pretty': true, 'x-designable-bar': 'Markdown.Void.DesignableBar',
'x-component': 'Markdown.Void', 'x-decorator': 'CardItem',
}; 'x-read-pretty': true,
if (isGridBlock(schema)) { 'x-component': 'Markdown.Void',
path.pop(); };
path.pop(); if (isGridBlock(schema)) {
data = generateGridBlock(data); path.pop();
} else if (isGrid(schema)) { path.pop();
data = generateGridBlock(data); data = generateGridBlock(data);
} } else if (isGrid(schema)) {
if (data) { data = generateGridBlock(data);
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
} }
await createSchema(s); if (data) {
} let s;
setVisible(false); if (isGrid(schema)) {
}} s = appendChild(data, [...path]);
> } else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
</Menu.Item> } else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
setVisible(false);
}}
>
Markdown
</Menu.Item>
</Menu.ItemGroup>
</Menu> </Menu>
} }
> >

View File

@ -1,111 +1,111 @@
import React, { useContext } from 'react'; // import React, { useContext } from 'react';
import { // import {
Column, // Column,
ColumnConfig, // ColumnConfig,
Line, // Line,
LineConfig, // LineConfig,
Pie, // Pie,
PieConfig, // PieConfig,
Bar, // Bar,
BarConfig, // BarConfig,
} from '@ant-design/charts'; // } from '@ant-design/charts';
import { // import {
connect, // connect,
mapProps, // mapProps,
observer, // observer,
useField, // useField,
useFieldSchema, // useFieldSchema,
mapReadPretty, // mapReadPretty,
} from '@formily/react'; // } from '@formily/react';
import { Button, Dropdown, Input as AntdInput, Menu, Space } from 'antd'; // import { Button, Dropdown, Input as AntdInput, Menu, Space } from 'antd';
import { InputProps, TextAreaProps } from 'antd/lib/input'; // import { InputProps, TextAreaProps } from 'antd/lib/input';
import { Display } from '../display'; // import { Display } from '../display';
import { LoadingOutlined, MenuOutlined, DragOutlined } from '@ant-design/icons'; // import { LoadingOutlined, MenuOutlined, DragOutlined } from '@ant-design/icons';
import micromark from 'micromark'; // import micromark from 'micromark';
import { useDesignable } from '../../components/schema-renderer'; // import { useDesignable } from '../../components/schema-renderer';
import { useState } from 'react'; // import { useState } from 'react';
import AddNew from '../add-new'; // import AddNew from '../add-new';
import cls from 'classnames'; // import cls from 'classnames';
import { DraggableBlockContext } from '../../components/drag-and-drop'; // import { DraggableBlockContext } from '../../components/drag-and-drop';
import { uid } from '@formily/shared'; // import { uid } from '@formily/shared';
import { removeSchema, updateSchema } from '..'; // import { removeSchema, updateSchema } from '..';
import { isGridRowOrCol } from '../grid'; // import { isGridRowOrCol } from '../grid';
export const Chart: any = {}; // export const Chart: any = {};
Chart.Column = observer((props: any) => { // Chart.Column = observer((props: any) => {
return <Column {...props.config} />; // return <Column {...props.config} />;
}); // });
Chart.Line = observer((props: any) => { // Chart.Line = observer((props: any) => {
return <Line {...props.config} />; // return <Line {...props.config} />;
}); // });
Chart.Pie = observer((props: any) => { // Chart.Pie = observer((props: any) => {
return <Pie {...props.config} />; // return <Pie {...props.config} />;
}); // });
Chart.Bar = observer((props: any) => { // Chart.Bar = observer((props: any) => {
return <Bar {...props.config} />; // return <Bar {...props.config} />;
}); // });
Chart.DesignableBar = observer((props) => { // Chart.DesignableBar = observer((props) => {
const field = useField(); // const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable(); // const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false); // const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext); // const { dragRef } = useContext(DraggableBlockContext);
if (!designable) { // if (!designable) {
return null; // return null;
} // }
return ( // return (
<div className={cls('designable-bar', { active: visible })}> // <div className={cls('designable-bar', { active: visible })}>
<span // <span
onClick={(e) => { // onClick={(e) => {
e.stopPropagation(); // e.stopPropagation();
}} // }}
className={cls('designable-bar-actions', { active: visible })} // className={cls('designable-bar-actions', { active: visible })}
> // >
<Space size={'small'}> // <Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost /> // <AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />} // {dragRef && <DragOutlined ref={dragRef} />}
<Dropdown // <Dropdown
trigger={['click']} // trigger={['click']}
visible={visible} // visible={visible}
onVisibleChange={(visible) => { // onVisibleChange={(visible) => {
setVisible(visible); // setVisible(visible);
}} // }}
overlay={ // overlay={
<Menu> // <Menu>
<Menu.Item // <Menu.Item
key={'update'} // key={'update'}
onClick={() => { // onClick={() => {
field.readPretty = false; // field.readPretty = false;
setVisible(false); // setVisible(false);
}} // }}
> // >
// 修改文本段
</Menu.Item> // </Menu.Item>
<Menu.Divider /> // <Menu.Divider />
<Menu.Item // <Menu.Item
key={'delete'} // key={'delete'}
onClick={async () => { // onClick={async () => {
const removed = deepRemove(); // const removed = deepRemove();
// console.log({ removed }) // // console.log({ removed })
const last = removed.pop(); // const last = removed.pop();
if (isGridRowOrCol(last)) { // if (isGridRowOrCol(last)) {
await removeSchema(last); // await removeSchema(last);
} // }
}} // }}
> // >
// 删除当前文本
</Menu.Item> // </Menu.Item>
</Menu> // </Menu>
} // }
> // >
<MenuOutlined /> // <MenuOutlined />
</Dropdown> // </Dropdown>
</Space> // </Space>
</span> // </span>
</div> // </div>
); // );
}); // });

View File

@ -26,7 +26,7 @@ import {
message, message,
Spin, Spin,
} from 'antd'; } from 'antd';
import { options, interfaces } from './interfaces'; import { options, interfaces, getDefaultFields } from './interfaces';
import { import {
DeleteOutlined, DeleteOutlined,
DatabaseOutlined, DatabaseOutlined,
@ -53,16 +53,21 @@ export const DatabaseCollection = observer((props) => {
const [activeIndex, setActiveIndex] = useState(0); const [activeIndex, setActiveIndex] = useState(0);
const form = useForm(); const form = useForm();
const [newValue, setNewValue] = useState(''); const [newValue, setNewValue] = useState('');
const { refresh } = useCollectionContext(); const { loading, refresh, data } = useCollectionContext();
const { run, loading } = useRequest('collections:findAll', { useEffect(() => {
formatResult: (result) => result?.data, field.setValue(data);
onSuccess(data) { console.log('onSuccess', data);
field.setValue(data); }, [data]);
console.log('onSuccess', data);
}, // const { run, loading } = useRequest('collections:findAll', {
manual: true, // formatResult: (result) => result?.data,
}); // onSuccess(data) {
// // field.setValue(data);
// // console.log('onSuccess', data);
// },
// manual: true,
// });
return ( return (
<div> <div>
@ -74,12 +79,12 @@ export const DatabaseCollection = observer((props) => {
}} }}
onClick={async () => { onClick={async () => {
setVisible(true); setVisible(true);
await run(); // await run();
if (field.value?.length === 0) { if (field.value?.length === 0) {
field.push({ field.push({
name: `t_${uid()}`, name: `t_${uid()}`,
unsaved: true, unsaved: true,
fields: [], fields: getDefaultFields(),
}); });
} }
}} }}
@ -127,7 +132,7 @@ export const DatabaseCollection = observer((props) => {
const data = { const data = {
name: `t_${uid()}`, name: `t_${uid()}`,
title: value, title: value,
fields: [], fields: getDefaultFields(),
}; };
field.push(data); field.push(data);
setActiveIndex(field.value.length - 1); setActiveIndex(field.value.length - 1);
@ -160,28 +165,30 @@ export const DatabaseCollection = observer((props) => {
> >
{item.title || '未命名'}{' '} {item.title || '未命名'}{' '}
{item.unsaved ? '(未保存)' : ''} {item.unsaved ? '(未保存)' : ''}
<DeleteOutlined {item.privilege !== 'undelete' && (
onClick={async (e) => { <DeleteOutlined
e.stopPropagation(); onClick={async (e) => {
field.remove(index); e.stopPropagation();
if (field.value?.length === 0) { field.remove(index);
field.push({ if (field.value?.length === 0) {
name: `t_${uid()}`, field.push({
unsaved: true, name: `t_${uid()}`,
fields: [], unsaved: true,
}); fields: getDefaultFields(),
} });
if (activeIndex === index) { }
setActiveIndex(0); if (activeIndex === index) {
} else if (activeIndex > index) { setActiveIndex(0);
setActiveIndex(activeIndex - 1); } else if (activeIndex > index) {
} setActiveIndex(activeIndex - 1);
if (item.name) { }
await deleteCollection(item.name); if (item.name) {
await refresh(); await deleteCollection(item.name);
} await refresh();
}} }
/> }}
/>
)}
</div> </div>
</Select.Option> </Select.Option>
); );
@ -206,24 +213,20 @@ export const DatabaseCollection = observer((props) => {
} catch (error) {} } catch (error) {}
}} }}
> >
{loading ? ( <FormLayout layout={'vertical'}>
<Spin /> <RecursionField
) : ( name={activeIndex}
<FormLayout layout={'vertical'}> schema={
<RecursionField new Schema({
name={activeIndex} type: 'object',
schema={ properties: schema.properties,
new Schema({ })
type: 'object', }
properties: schema.properties, />
}) {/* <FormConsumer>
}
/>
{/* <FormConsumer>
{(form) => <pre>{JSON.stringify(form.values, null, 2)}</pre>} {(form) => <pre>{JSON.stringify(form.values, null, 2)}</pre>}
</FormConsumer> */} </FormConsumer> */}
</FormLayout> </FormLayout>
)}
</Modal> </Modal>
</div> </div>
); );
@ -237,7 +240,6 @@ export const DatabaseField: any = observer((props) => {
} }
}, []); }, []);
const [activeKey, setActiveKey] = useState(null); const [activeKey, setActiveKey] = useState(null);
console.log('DatabaseField', field);
return ( return (
<div> <div>
<Collapse <Collapse
@ -249,13 +251,19 @@ export const DatabaseField: any = observer((props) => {
accordion accordion
> >
{field.value?.map((item, index) => { {field.value?.map((item, index) => {
if (!item.interface) {
return;
}
const schema = cloneDeep(interfaces.get(item.interface)); const schema = cloneDeep(interfaces.get(item.interface));
if (!schema) {
console.error('schema invalid');
return;
}
const path = field.address.concat(index); const path = field.address.concat(index);
const errors = field.form.queryFeedbacks({ const errors = field.form.queryFeedbacks({
type: 'error', type: 'error',
address: `*(${path},${path}.*)`, address: `*(${path},${path}.*)`,
}); });
console.log('item.key', item.key);
return ( return (
<Collapse.Panel <Collapse.Panel
header={ header={
@ -263,22 +271,30 @@ export const DatabaseField: any = observer((props) => {
{(item.uiSchema && item.uiSchema.title) || ( {(item.uiSchema && item.uiSchema.title) || (
<i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i> <i style={{ color: 'rgba(0, 0, 0, 0.25)' }}></i>
)}{' '} )}{' '}
<Tag>{schema.title}</Tag> <Tag
className={item.privilege ? cls(item.privilege) : undefined}
>
{schema.title}
</Tag>
<span style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}> <span style={{ color: 'rgba(0, 0, 0, 0.25)', fontSize: 14 }}>
{item.name} {item.name}
</span> </span>
</> </>
} }
extra={[ extra={
<Badge key={'1'} count={errors.length} />, item.privilege === 'undelete'
<DeleteOutlined ? []
key={'2'} : [
onClick={(e) => { <Badge key={'1'} count={errors.length} />,
e.stopPropagation(); <DeleteOutlined
field.remove(index); key={'2'}
}} onClick={(e) => {
/>, e.stopPropagation();
]} field.remove(index);
}}
/>,
]
}
key={item.key} key={item.key}
> >
<RecursionField <RecursionField
@ -325,6 +341,9 @@ export const DatabaseField: any = observer((props) => {
name: `f_${uid()}`, name: `f_${uid()}`,
interface: info.key, interface: info.key,
}; };
if (schema.initialize) {
schema.initialize(data);
}
field.push(data); field.push(data);
setActiveKey(data.key); setActiveKey(data.key);
console.log('info.key', field.value); console.log('info.key', field.value);

View File

@ -1,4 +1,5 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { omit } from 'lodash';
import { defaultProps } from './properties'; import { defaultProps } from './properties';
export const checkbox: ISchema = { export const checkbox: ISchema = {

View File

@ -1,5 +1,5 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { defaultProps } from './properties'; import { dateTimeProps, defaultProps } from './properties';
export const createdAt: ISchema = { export const createdAt: ISchema = {
name: 'createdAt', name: 'createdAt',
@ -13,7 +13,7 @@ export const createdAt: ISchema = {
// name, // name,
uiSchema: { uiSchema: {
type: 'datetime', type: 'datetime',
// title, title: '创建时间',
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': {}, 'x-component-props': {},
'x-read-pretty': true, 'x-read-pretty': true,
@ -23,6 +23,7 @@ export const createdAt: ISchema = {
}, },
properties: { properties: {
...defaultProps, ...defaultProps,
...dateTimeProps,
}, },
operations: [ operations: [
{ label: '等于', value: 'eq' }, { label: '等于', value: 'eq' },

View File

@ -14,7 +14,7 @@ export const createdBy: ISchema = {
// name, // name,
uiSchema: { uiSchema: {
type: 'object', type: 'object',
// title, title: '创建人',
'x-component': 'Select.Drawer', 'x-component': 'Select.Drawer',
'x-component-props': {}, 'x-component-props': {},
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',

View File

@ -1,5 +1,5 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { defaultProps } from './properties'; import { dateTimeProps, defaultProps } from './properties';
export const datetime: ISchema = { export const datetime: ISchema = {
name: 'datetime', name: 'datetime',
@ -14,13 +14,16 @@ export const datetime: ISchema = {
type: 'datetime', type: 'datetime',
// title, // title,
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': {}, 'x-component-props': {
showTime: false,
},
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-designable-bar': 'DatePicker.DesignableBar', 'x-designable-bar': 'DatePicker.DesignableBar',
} as ISchema, } as ISchema,
}, },
properties: { properties: {
...defaultProps, ...defaultProps,
...dateTimeProps,
}, },
operations: [ operations: [
{ label: '等于', value: 'eq' }, { label: '等于', value: 'eq' },

View File

@ -1,12 +1,26 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { set } from 'lodash'; import { cloneDeep, set } from 'lodash';
import * as types from './types'; import * as types from './types';
import { uid } from '@formily/shared';
export const interfaces = new Map<string, ISchema>(); export const interfaces = new Map<string, ISchema>();
const fields = {}; const fields = {};
const groupLabels = {}; const groupLabels = {};
export function getDefaultFields() {
const defaults = ['createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
return defaults.map(key => {
return {
interface: key,
key: uid(),
name: uid(),
privilege: 'undelete',
...cloneDeep(interfaces.get(key)?.default),
}
});
}
export function registerField(group: string, type: string, schema) { export function registerField(group: string, type: string, schema) {
fields[group] = fields[group] || {}; fields[group] = fields[group] || {};
set(fields, [group, type], schema); set(fields, [group, type], schema);
@ -25,6 +39,7 @@ Object.keys(types).forEach((type) => {
registerGroupLabel('basic', '基本类型'); registerGroupLabel('basic', '基本类型');
registerGroupLabel('choices', '选择类型'); registerGroupLabel('choices', '选择类型');
registerGroupLabel('media', '多媒体类型'); registerGroupLabel('media', '多媒体类型');
registerGroupLabel('datetime', '日期和时间');
registerGroupLabel('relation', '关系类型'); registerGroupLabel('relation', '关系类型');
registerGroupLabel('systemInfo', '系统信息'); registerGroupLabel('systemInfo', '系统信息');
registerGroupLabel('others', '其他类型'); registerGroupLabel('others', '其他类型');

View File

@ -1,5 +1,6 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { defaultProps } from './properties'; import { defaultProps } from './properties';
import { uid } from '@formily/shared';
export const linkTo: ISchema = { export const linkTo: ISchema = {
name: 'linkTo', name: 'linkTo',
@ -19,8 +20,41 @@ export const linkTo: ISchema = {
'x-designable-bar': 'Select.Drawer.DesignableBar', 'x-designable-bar': 'Select.Drawer.DesignableBar',
} as ISchema, } as ISchema,
}, },
initialize: (values: any) => {
if (values.dataType === 'belongsToMany') {
if (!values.through) {
values.through = `t_${uid()}`;
}
if (!values.foreignKey) {
values.foreignKey = `f_${uid()}`;
}
if (!values.otherKey) {
values.otherKey = `f_${uid()}`;
}
if (!values.sourceKey) {
values.sourceKey = 'id';
}
if (!values.targetKey) {
values.targetKey = 'id';
}
}
},
properties: { properties: {
...defaultProps, ...defaultProps,
target: {
type: 'string',
title: '要关联的数据表',
required: true,
'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'],
'x-decorator': 'FormItem',
'x-component': 'Select',
},
'uiSchema.x-component-props.multiple': {
type: 'boolean',
'x-content': '允许关联多条记录',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
},
}, },
operations: [ operations: [
{ label: '等于', value: 'eq' }, { label: '等于', value: 'eq' },

View File

@ -14,7 +14,9 @@ export const multipleSelect: ISchema = {
type: 'array', type: 'array',
// title, // title,
'x-component': 'Select', 'x-component': 'Select',
'x-component-props': {}, 'x-component-props': {
mode: 'multiple',
},
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-designable-bar': 'Select.DesignableBar', 'x-designable-bar': 'Select.DesignableBar',
enum: [], enum: [],

View File

@ -14,12 +14,31 @@ export const number: ISchema = {
type: 'number', type: 'number',
// title, // title,
'x-component': 'InputNumber', 'x-component': 'InputNumber',
'x-component-props': {
stringMode: true,
step: '0',
},
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-designable-bar': 'InputNumber.DesignableBar', 'x-designable-bar': 'InputNumber.DesignableBar',
} as ISchema, } as ISchema,
}, },
properties: { properties: {
...defaultProps, ...defaultProps,
'uiSchema.x-component-props.step': {
type: 'string',
title: '精度',
'x-component': 'Select',
'x-decorator': 'FormItem',
default: '0',
enum: [
{ value: '0', label: '1' },
{ value: '0.1', label: '1.0' },
{ value: '0.01', label: '1.00' },
{ value: '0.001', label: '1.000' },
{ value: '0.0001', label: '1.0000' },
{ value: '0.00001', label: '1.00000' },
],
},
}, },
operations: [ operations: [
{ label: '等于', value: 'eq' }, { label: '等于', value: 'eq' },

View File

@ -14,12 +14,31 @@ export const percent: ISchema = {
type: 'string', type: 'string',
// title, // title,
'x-component': 'InputNumber', 'x-component': 'InputNumber',
'x-component-props': {
stringMode: true,
step: '0',
},
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-designable-bar': 'InputNumber.DesignableBar', 'x-designable-bar': 'InputNumber.DesignableBar',
} as ISchema, } as ISchema,
}, },
properties: { properties: {
...defaultProps ...defaultProps,
'uiSchema.x-component-props.step': {
type: 'string',
title: '精度',
'x-component': 'Select',
'x-decorator': 'FormItem',
default: '0',
enum: [
{ value: '0', label: '1' },
{ value: '0.1', label: '1.0' },
{ value: '0.01', label: '1.00' },
{ value: '0.001', label: '1.000' },
{ value: '0.0001', label: '1.0000' },
{ value: '0.00001', label: '1.00000' },
]
},
}, },
operations: [ operations: [
{ label: '等于', value: 'eq' }, { label: '等于', value: 'eq' },

View File

@ -1,9 +1,10 @@
import { ISchema } from "@formily/react"; import { ISchema } from '@formily/react';
export const dataType: ISchema = { export const dataType: ISchema = {
type: 'string', type: 'string',
title: '数据类型', title: '数据类型',
required: true, required: true,
'x-disabled': true,
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',
'x-component': 'Select', 'x-component': 'Select',
enum: [ enum: [
@ -24,7 +25,61 @@ export const dataType: ISchema = {
{ label: 'BelongsTo', value: 'belongsTo' }, { label: 'BelongsTo', value: 'belongsTo' },
{ label: 'BelongsToMany', value: 'belongsToMany' }, { label: 'BelongsToMany', value: 'belongsToMany' },
], ],
} };
export const dateTimeProps: { [key: string]: ISchema } = {
dateFormat: {
type: 'string',
title: '日期格式',
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'YYYY-MM-DD',
enum: [
{
label: '年/月/日',
value: 'YYYY/MM/DD',
},
{
label: '年-月-日',
value: 'YYYY-MM-DD',
},
{
label: '日/月/年',
value: 'DD/MM/YYYY',
},
],
},
showTime: {
type: 'boolean',
'x-decorator': 'FormItem',
'x-component': 'Checkbox',
'x-content': '显示时间',
'x-reactions': [
`{{(field) => {
field.query('..[].timeFormat').take(f => {
f.display = field.value ? 'visible' : 'none';
});
}}}`,
],
},
timeFormat: {
type: 'string',
title: '时间格式',
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'HH:mm:ss',
enum: [
{
label: '24小时制',
value: 'HH:mm:ss',
},
{
label: '12小时制',
value: 'hh:mm:ss a',
},
],
},
};
export const dataSource: ISchema = { export const dataSource: ISchema = {
type: 'array', type: 'array',
@ -53,7 +108,7 @@ export const dataSource: ISchema = {
type: 'void', type: 'void',
'x-component': 'ArrayTable.Column', 'x-component': 'ArrayTable.Column',
'x-component-props': { title: '选项值' }, 'x-component-props': { title: '选项值' },
"x-hidden": true, 'x-hidden': true,
properties: { properties: {
value: { value: {
type: 'string', type: 'string',

View File

@ -1,5 +1,6 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { defaultProps } from './properties'; import { defaultProps } from './properties';
import { uid } from '@formily/shared';
export const subTable: ISchema = { export const subTable: ISchema = {
name: 'subTable', name: 'subTable',
@ -20,6 +21,14 @@ export const subTable: ISchema = {
enum: [], enum: [],
} as ISchema, } as ISchema,
}, },
initialize: (values: any) => {
if (!values.target) {
values.target = `t_${uid()}`;
}
if (!values.foreignKey) {
values.foreignKey = `f_${uid()}`;
}
},
properties: { properties: {
...defaultProps, ...defaultProps,
'children': { 'children': {

View File

@ -20,6 +20,23 @@ export const time: ISchema = {
}, },
properties: { properties: {
...defaultProps, ...defaultProps,
'uiSchema.x-component-props.format': {
type: 'string',
title: '时间格式',
'x-component': 'Radio.Group',
'x-decorator': 'FormItem',
default: 'HH:mm:ss',
enum: [
{
label: '24小时制',
value: 'HH:mm:ss',
},
{
label: '12小时制',
value: 'hh:mm:ss a',
},
],
},
}, },
operations: [ operations: [
{ label: '等于', value: 'eq' }, { label: '等于', value: 'eq' },

View File

@ -1,5 +1,5 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { defaultProps } from './properties'; import { dateTimeProps, defaultProps } from './properties';
export const updatedAt: ISchema = { export const updatedAt: ISchema = {
name: 'updatedAt', name: 'updatedAt',
@ -13,7 +13,7 @@ export const updatedAt: ISchema = {
// name, // name,
uiSchema: { uiSchema: {
type: 'datetime', type: 'datetime',
// title, title: '最后更新时间',
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': {}, 'x-component-props': {},
'x-read-pretty': true, 'x-read-pretty': true,
@ -23,6 +23,7 @@ export const updatedAt: ISchema = {
}, },
properties: { properties: {
...defaultProps, ...defaultProps,
...dateTimeProps,
}, },
operations: [ operations: [
{ label: '等于', value: 'eq' }, { label: '等于', value: 'eq' },

View File

@ -14,7 +14,7 @@ export const updatedBy: ISchema = {
// name, // name,
uiSchema: { uiSchema: {
type: 'object', type: 'object',
// title, title: '最后修改人',
'x-component': 'Select.Drawer', 'x-component': 'Select.Drawer',
'x-component-props': {}, 'x-component-props': {},
'x-decorator': 'FormItem', 'x-decorator': 'FormItem',

View File

@ -21,3 +21,8 @@
overflow: auto; overflow: auto;
} }
} }
.ant-tag.disabled {
color: rgba(0, 0, 0, 0.25);
cursor: not-allowed;
}

View File

@ -55,7 +55,7 @@ export const FieldDesignableBar = observer((props) => {
className={cls('designable-bar-actions', { active: visible })} className={cls('designable-bar-actions', { active: visible })}
> >
<Space size={'small'}> <Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost /> <AddNew.FormItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />} {dragRef && <DragOutlined ref={dragRef} />}
<Dropdown <Dropdown
trigger={['click']} trigger={['click']}

View File

@ -49,8 +49,8 @@ export function useVisible(name, defaultValue = false) {
export const DesignableBarContext = createContext(null); export const DesignableBarContext = createContext(null);
const [PageTitleContextProvider, usePageTitleContext] = constate(() => { const [PageTitleContextProvider, usePageTitleContext] = constate(({ defaultPageTitle }) => {
return useState(null); return useState(defaultPageTitle);
}); });
export { PageTitleContextProvider, usePageTitleContext }; export { PageTitleContextProvider, usePageTitleContext };

View File

@ -72,7 +72,7 @@ import { onFieldChange } from '@formily/core';
export const MenuModeContext = createContext(null); export const MenuModeContext = createContext(null);
const SideMenu = (props: any) => { const SideMenu = (props: any) => {
const { selectedKey, onSelect, path } = props; const { selectedKey, defaultSelectedKeys, onSelect, path } = props;
const { schema } = useDesignable(); const { schema } = useDesignable();
if (!selectedKey) { if (!selectedKey) {
return null; return null;
@ -101,11 +101,11 @@ const SideMenu = (props: any) => {
}; };
export const Menu: any = observer((props: any) => { export const Menu: any = observer((props: any) => {
const { mode, onSelect, sideMenuRef, ...others } = props; const { mode, onSelect, sideMenuRef, defaultSelectedKeys = [], ...others } = props;
const { designable, schema } = useDesignable(); const { designable, schema } = useDesignable();
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
console.log('Menu.schema', schema, fieldSchema); console.log('Menu.schema', schema, fieldSchema);
const [selectedKey, setSelectedKey] = useState(null); const [selectedKey, setSelectedKey] = useState(defaultSelectedKeys[0]||null);
const ref = useRef(); const ref = useRef();
const path = useSchemaPath(); const path = useSchemaPath();
const child = schema.properties && schema.properties[selectedKey]; const child = schema.properties && schema.properties[selectedKey];
@ -133,6 +133,7 @@ export const Menu: any = observer((props: any) => {
return ( return (
<MenuModeContext.Provider value={mode}> <MenuModeContext.Provider value={mode}>
<AntdMenu <AntdMenu
defaultSelectedKeys={defaultSelectedKeys}
{...others} {...others}
mode={mode === 'mix' ? 'horizontal' : mode} mode={mode === 'mix' ? 'horizontal' : mode}
onSelect={(info) => { onSelect={(info) => {
@ -163,12 +164,13 @@ export const Menu: any = observer((props: any) => {
<SideMenu <SideMenu
path={path} path={path}
onSelect={(info) => { onSelect={(info) => {
const keyPath = [selectedKey, ...info.keyPath]; const keyPath = [selectedKey, ...[...info.keyPath].reverse()];
const selectedSchema = findPropertyByPath(schema, keyPath); const selectedSchema = findPropertyByPath(schema, keyPath);
console.log('keyPath', keyPath, selectedSchema); console.log('keyPath', keyPath, selectedSchema);
onSelect && onSelect &&
onSelect({ ...info, keyPath, schema: selectedSchema }); onSelect({ ...info, keyPath, schema: selectedSchema });
}} }}
defaultSelectedKeys={defaultSelectedKeys||[]}
selectedKey={selectedKey} selectedKey={selectedKey}
sideMenuRef={sideMenuRef} sideMenuRef={sideMenuRef}
/> />

View File

@ -423,10 +423,11 @@ export abstract class Relation extends Field {
public targetTableInit() { public targetTableInit() {
const { target, fields = [] } = this.options; const { target, fields = [] } = this.options;
if (target && fields.length) { const children = fields.concat(this.options.children || []);
if (target && children.length) {
this.context.database.table({ this.context.database.table({
name: target, name: target,
fields, fields: children,
}); });
} }
} }

View File

@ -7,6 +7,6 @@ export const create = async (ctx: actions.Context, next: actions.Next) => {
await actions.common.create(ctx, async () => {}); await actions.common.create(ctx, async () => {});
const { associated } = ctx.action.params; const { associated } = ctx.action.params;
await associated.migrate(); await associated.migrate();
console.log('associated.migrate'); // console.log('associated.migrate');
await next(); await next();
} }

View File

@ -32,7 +32,7 @@ export const createOrUpdate = async (ctx: actions.Context, next: actions.Next) =
await collection.updateAssociations(values); await collection.updateAssociations(values);
await collection.migrate(); await collection.migrate();
} catch (error) { } catch (error) {
console.log('error.errors', error.errors) // console.log('error.errors', error.errors)
throw error; throw error;
} }
ctx.body = collection; ctx.body = collection;

View File

@ -20,6 +20,10 @@ export default {
name: 'title', name: 'title',
required: true, required: true,
}, },
{
type: 'string',
name: 'privilege',
},
{ {
type: 'json', type: 'json',
name: 'options', name: 'options',
@ -30,5 +34,10 @@ export default {
name: 'fields', name: 'fields',
sourceKey: 'name', sourceKey: 'name',
}, },
{
type: 'belongsTo',
name: 'uiSchema',
target: 'ui_schemas',
},
], ],
} as TableOptions; } as TableOptions;

View File

@ -31,6 +31,10 @@ export default {
type: 'string', type: 'string',
name: 'dataType', name: 'dataType',
}, },
{
type: 'string',
name: 'privilege',
},
{ {
type: 'hasMany', type: 'hasMany',
name: 'children', name: 'children',
@ -49,7 +53,6 @@ export default {
type: 'belongsTo', type: 'belongsTo',
name: 'uiSchema', name: 'uiSchema',
target: 'ui_schemas', target: 'ui_schemas',
defaultValue: {},
}, },
{ {
type: 'json', type: 'json',

View File

@ -41,7 +41,9 @@ export class Collection extends Model {
} }
async getNestedFields() { async getNestedFields() {
const fields = await this.getFields(); const fields = await this.getFields({
order: [['sort', 'asc']],
});
const items = []; const items = [];
for (const field of fields) { for (const field of fields) {
items.push(await field.toProps()); items.push(await field.toProps());
@ -58,7 +60,7 @@ export class Collection extends Model {
*/ */
async loadTableOptions(opts: any = {}) { async loadTableOptions(opts: any = {}) {
const options = await this.toProps(); const options = await this.toProps();
console.log(JSON.stringify(options, null, 2)); // console.log(JSON.stringify(options, null, 2));
const table = this.database.table(options); const table = this.database.table(options);
return table; return table;
} }

View File

@ -3,7 +3,6 @@ import { Model } from '@nocobase/database';
export class Field extends Model { export class Field extends Model {
static async create(value?: any, options?: any): Promise<any> { static async create(value?: any, options?: any): Promise<any> {
// console.log({ value });
const attributes = this.toAttributes(value); const attributes = this.toAttributes(value);
// @ts-ignore // @ts-ignore
const model: Model = await super.create(attributes, options); const model: Model = await super.create(attributes, options);
@ -11,13 +10,16 @@ export class Field extends Model {
} }
static toAttributes(value = {}): any { static toAttributes(value = {}): any {
const data = _.cloneDeep(value); const data: any = _.cloneDeep(value);
const keys = [ const keys = [
...Object.keys(this.rawAttributes), ...Object.keys(this.rawAttributes),
...Object.keys(this.associations), ...Object.keys(this.associations),
]; ];
if (!data.dataType && data.type) {
data.dataType = data.type;
}
const attrs = _.pick(data, keys); const attrs = _.pick(data, keys);
const options = _.omit(data, keys); const options = _.omit(data, [...keys, 'type']);
return { ...attrs, options }; return { ...attrs, options };
} }

View File

@ -11,10 +11,86 @@ export default async function (this: Application, options = {}) {
database.import({ database.import({
directory: path.resolve(__dirname, 'collections'), directory: path.resolve(__dirname, 'collections'),
}); });
database.getModel('fields').beforeCreate((model) => { const [Collection, Field] = database.getModels(['collections', 'fields']);
Field.beforeCreate(async (model) => {
if (!model.get('name')) { if (!model.get('name')) {
model.set('name', model.get('key')); model.set('name', model.get('key'));
} }
if (!model.get('collection_name') && model.get('parentKey')) {
const field = await Field.findByPk(model.get('parentKey'));
if (field) {
const { target } = field.get('options') || {};
if (target) {
model.set('collection_name', target);
}
}
}
});
Field.beforeUpdate(async (model) => {
if (!model.get('collection_name') && model.get('parentKey')) {
const field = await Field.findByPk(model.get('parentKey'));
if (field) {
const { target } = field.get('options') || {};
if (target) {
model.set('collection_name', target);
}
}
}
});
Field.afterCreate(async (model) => {
console.log('afterCreate');
if (model.get('interface') !== 'subTable') {
return;
}
const { target } = model.get('options') || {};
// const uiSchemaKey = model.get('ui_schema_key');
// console.log({ uiSchemaKey })
try {
let collection = await Collection.findOne({
where: {
name: target,
},
});
if (!collection) {
collection = await Collection.create({
name: target,
// ui_schema_key: uiSchemaKey,
});
}
// if (model.get('ui_schema_key')) {
// collection.set('ui_schema_key', model.get('ui_schema_key'));
// await collection.save({ hooks: false });
// }
await collection.migrate();
} catch (error) {
throw error;
}
});
Field.afterUpdate(async (model) => {
console.log('afterUpdate');
if (model.get('interface') !== 'subTable') {
return;
}
const { target } = model.get('options') || {};
try {
let collection = await Collection.findOne({
where: {
name: target,
},
});
if (!collection) {
collection = await Collection.create({
name: target,
});
}
// if (model.get('ui_schema_key')) {
// collection.set('ui_schema_key', model.get('ui_schema_key'));
// await collection.save({ hooks: false });
// }
await collection.migrate();
} catch (error) {
throw error;
}
}); });
this.resourcer.registerActionHandler('collections.fields:create', create); this.resourcer.registerActionHandler('collections.fields:create', create);
this.resourcer.registerActionHandler('collections:findAll', findAll); this.resourcer.registerActionHandler('collections:findAll', findAll);

View File

@ -1,12 +1,12 @@
import _ from 'lodash'; import _ from 'lodash';
import { Model } from '@nocobase/database'; import { Model } from '@nocobase/database';
import deepmerge from 'deepmerge'; import { merge } from '../utils';
export class UISchema extends Model { export class UISchema extends Model {
static async create(value?: any, options?: any): Promise<any> { static async create(value?: any, options?: any): Promise<any> {
// console.log({ value }); // console.log({ value });
const attributes = this.toAttributes(_.cloneDeep(value)); const attributes = this.toAttributes(_.cloneDeep(value));
console.log({ attributes }) // console.log({ attributes })
// @ts-ignore // @ts-ignore
const model: Model = await super.create(attributes, options); const model: Model = await super.create(attributes, options);
if (!attributes.children) { if (!attributes.children) {
@ -34,7 +34,9 @@ export class UISchema extends Model {
]; ];
const attrs = _.pick(data, keys); const attrs = _.pick(data, keys);
const options = _.omit(data, keys); const options = _.omit(data, keys);
return { ...attrs, options: deepmerge(opts, options) }; return {
...attrs, options: merge(opts, options)
};
} }
static properties2children(properties = []) { static properties2children(properties = []) {

View File

@ -7,165 +7,67 @@ export default {
// internal: true, // internal: true,
createdBy: false, createdBy: false,
updatedBy: false, updatedBy: false,
privilege: 'undelete',
fields: [ fields: [
{ {
interface: 'string', interface: 'string',
type: 'string', type: 'string',
name: 'username', name: 'nickname',
title: '用户名', uiSchema: {
unique: true,
required: true,
createOnly: true,
component: {
type: 'string', type: 'string',
showInTable: true, title: '昵称',
showInDetail: true, 'x-component': 'Input',
showInForm: true,
}, },
}, },
{ {
interface: 'email', interface: 'email',
type: 'string', type: 'string',
name: 'email', name: 'email',
title: '邮箱',
unique: true, unique: true,
required: true, privilege: 'undelete',
createOnly: true, uiSchema: {
component: {
type: 'string', type: 'string',
showInTable: true, title: '邮箱',
showInDetail: true, 'x-component': 'Input',
showInForm: true, require: true,
},
},
{
interface: 'string',
type: 'string',
name: 'nickname',
title: '昵称',
required: true,
component: {
type: 'string',
showInTable: true,
showInDetail: true,
showInForm: true,
},
},
{
interface: 'phone',
type: 'string',
name: 'phone',
unique: true,
title: '手机号',
component: {
type: 'string',
showInTable: true,
showInDetail: true,
showInForm: true,
}, },
}, },
{ {
interface: 'password', interface: 'password',
type: 'password', type: 'password',
name: 'password', name: 'password',
title: '密码', privilege: 'undelete',
hidden: true, uiSchema: {
component: { type: 'string',
type: 'password', title: '密码',
showInForm: true, 'x-component': 'Password',
}, },
}, },
{ {
interface: 'string', interface: 'password',
type: 'string', type: 'string',
name: 'token', name: 'token',
title: 'Token',
unique: true, unique: true,
hidden: true, hidden: true,
filterable: false, privilege: 'undelete',
developerMode: true, uiSchema: {
type: 'string',
title: 'Token',
'x-component': 'Password',
},
}, },
{ {
interface: 'string', interface: 'password',
type: 'string', type: 'string',
name: 'reset_token', name: 'reset_token',
title: 'Reset Token',
unique: true, unique: true,
hidden: true, hidden: true,
filterable: false, privilege: 'undelete',
developerMode: true, uiSchema: {
}, type: 'string',
], title: 'Reset Token',
actions: [ 'x-component': 'Password',
{ },
type: 'list',
name: 'list',
title: '查看',
},
{
type: 'destroy',
name: 'destroy',
title: '删除',
},
{
type: 'create',
name: 'create',
title: '新增',
viewName: 'form',
},
{
type: 'update',
name: 'update',
title: '编辑',
viewName: 'form',
},
],
views_v2: [
{
developerMode: true,
type: 'table',
name: 'table',
title: '全部数据',
labelField: 'nickname',
actions: [
{
name: 'create',
type: 'create',
title: '新增',
viewName: 'form',
},
{
name: 'destroy',
type: 'destroy',
title: '删除',
},
],
fields: ['email', 'nickname', 'phone', 'roles'],
detailsOpenMode: 'drawer', // window
details: ['form'],
sort: ['id'],
},
{
developerMode: true,
type: 'descriptions',
name: 'descriptions',
title: '详情',
fields: ['email', 'nickname', 'phone', 'roles'],
actions: [
{
name: 'update',
type: 'update',
title: '编辑',
viewName: 'form',
},
],
},
{
developerMode: true,
type: 'form',
name: 'form',
title: '表单',
fields: ['email', 'nickname', 'phone', 'password', 'roles'],
}, },
], ],
} as TableOptions; } as TableOptions;