feat: actions & views (#18)

* feat: 视图新增支持 associated 的情况

* fix: 解决关系数据的视图问题

* 表单数据初始化

* feat: sortable table

* 排序

* 表格细节调整

* feat: field actions
This commit is contained in:
chenos 2020-11-19 21:12:15 +08:00 committed by GitHub
parent b0ba1472bb
commit eb93f0734f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 595 additions and 289 deletions

View File

@ -33,12 +33,14 @@
"@nocobase/server": "^0.3.0-alpha.0",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.2.23",
"array-move": "^3.0.1",
"concurrently": "^5.3.0",
"lint-staged": "^10.0.7",
"nodemon": "^2.0.6",
"prettier": "^1.19.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-sortable-hoc": "^1.11.0",
"styled-components": "^5.2.1",
"umi": "^3.2.23",
"yorkie": "^2.0.0"

View File

@ -43,7 +43,7 @@ const data = {
path: '/',
type: 'layout',
template: 'TopMenuLayout',
order: 1,
sort: 10,
children: [
{
title: '仪表盘',
@ -51,7 +51,7 @@ const data = {
path: '/dashboard',
icon: 'dashboard',
template: 'page1',
order: 2,
sort: 20,
},
{
title: '数据',
@ -59,7 +59,7 @@ const data = {
path: '/collections',
icon: 'dashboard',
template: 'SideMenuLayout',
order: 3,
sort: 30,
children: [
{
title: '页面3',
@ -67,7 +67,7 @@ const data = {
path: '/collections/page3',
icon: 'dashboard',
template: 'page3',
order: 5,
sort: 40,
},
{
title: '页面4',
@ -75,17 +75,17 @@ const data = {
path: '/collections/page4',
icon: 'dashboard',
template: 'page4',
order: 6,
},
{
title: '页面5',
type: 'collection',
path: '/collections/collection1',
icon: 'dashboard',
template: 'collection',
collection: 'collection1',
order: 7,
sort: 50,
},
// {
// title: '页面5',
// type: 'collection',
// path: '/collections/collection1',
// icon: 'dashboard',
// template: 'collection',
// collection: 'collection1',
// sort: 60,
// },
]
},
{
@ -94,7 +94,7 @@ const data = {
path: '/users',
icon: 'dashboard',
template: 'SideMenuLayout',
order: 3,
sort: 70,
children: [
{
title: '用户管理',
@ -103,7 +103,7 @@ const data = {
icon: 'dashboard',
template: 'collection',
collection: 'users',
order: 1,
sort: 80,
},
]
},
@ -113,7 +113,7 @@ const data = {
path: '/settings',
icon: 'dashboard',
template: 'SideMenuLayout',
order: 4,
sort: 90,
children: [
{
title: '页面与菜单',
@ -121,7 +121,7 @@ const data = {
collection: 'pages',
path: '/settings/pages',
icon: 'dashboard',
order: 1,
sort: 100,
},
{
title: '数据表配置',
@ -129,7 +129,7 @@ const data = {
collection: 'collections',
path: '/settings/collections',
icon: 'dashboard',
order: 2,
sort: 110,
},
{
title: '权限配置',
@ -137,7 +137,7 @@ const data = {
collection: 'roles',
path: '/settings/roles',
icon: 'dashboard',
order: 3,
sort: 120,
},
]
},
@ -173,117 +173,121 @@ const data = {
// console.log(options);
const associations: any = {};
if (options.fields) {
associations['fields'] = options.fields.map(item => ({
associations['fields'] = options.fields.map((item, sort) => ({
...item,
options: item,
sort,
}))
}
if (options.tabs) {
associations['tabs'] = options.tabs.map(item => ({
associations['tabs'] = options.tabs.map((item, sort) => ({
...item,
options: item,
sort,
}))
}
if (options.actions) {
associations['actions'] = options.actions.map(item => ({
associations['actions'] = options.actions.map((item, sort) => ({
...item,
options: item,
sort,
}))
}
if (options.views) {
associations['views'] = options.views.map(item => ({
associations['views'] = options.views.map((item, sort) => ({
...item,
options: item,
sort,
}))
}
await collection.updateAssociations(associations);
}
const actions = await Action.findAll();
// const actions = await Action.findAll();
for (const action of actions) {
const viewName = action.options.viewName;
console.log({viewName});
if (viewName) {
const view = await View.findOne({
where: {
name: viewName,
collection_name: action.collection_name
},
});
if (view) {
action.options.viewId = view.id;
console.log(action.options);
action.setDataValue('options', action.options);
action.changed('options', true);
await action.save();
}
}
}
const tabs = await Tab.findAll();
// for (const action of actions) {
// const viewName = action.options.viewName;
// console.log({viewName});
// if (viewName) {
// const view = await View.findOne({
// where: {
// name: viewName,
// collection_name: action.collection_name
// },
// });
// if (view) {
// action.options.viewId = view.id;
// console.log(action.options);
// action.setDataValue('options', action.options);
// action.changed('options', true);
// await action.save();
// }
// }
// }
// const tabs = await Tab.findAll();
for (const tab of tabs) {
const viewName = tab.options.viewName;
if (!viewName) {
continue;
}
let view: any;
if (tab.type === 'association') {
view = await View.findOne({
where: {
name: viewName,
collection_name: tab.options.association,
},
});
} else {
view = await View.findOne({
where: {
name: viewName,
collection_name: tab.collection_name,
},
});
}
if (view) {
tab.options.viewId = view.id;
tab.setDataValue('options', tab.options);
tab.changed('options', true);
await tab.save();
}
}
const views = await View.findAll();
for (const view of views) {
const detailsViewName = view.options.detailsViewName;
if (detailsViewName) {
const v = await View.findOne({
where: {
name: detailsViewName,
collection_name: view.collection_name
},
});
if (v) {
view.options.detailsViewId = v.id;
view.setDataValue('options', view.options);
view.changed('options', true);
await view.save();
}
}
const updateViewName = view.options.updateViewName;
if (updateViewName) {
const v = await View.findOne({
where: {
name: updateViewName,
collection_name: view.collection_name
},
});
if (v) {
view.options.updateViewId = v.id;
view.setDataValue('options', view.options);
view.changed('options', true);
await view.save();
}
}
console.log({detailsViewName, updateViewName});
}
// for (const tab of tabs) {
// const viewName = tab.options.viewName;
// if (!viewName) {
// continue;
// }
// let view: any;
// if (tab.type === 'association') {
// view = await View.findOne({
// where: {
// name: viewName,
// collection_name: tab.options.association,
// },
// });
// } else {
// view = await View.findOne({
// where: {
// name: viewName,
// collection_name: tab.collection_name,
// },
// });
// }
// if (view) {
// tab.options.viewId = view.id;
// tab.setDataValue('options', tab.options);
// tab.changed('options', true);
// await tab.save();
// }
// }
// const views = await View.findAll();
// for (const view of views) {
// const detailsViewName = view.options.detailsViewName;
// if (detailsViewName) {
// const v = await View.findOne({
// where: {
// name: detailsViewName,
// collection_name: view.collection_name
// },
// });
// if (v) {
// view.options.detailsViewId = v.id;
// view.setDataValue('options', view.options);
// view.changed('options', true);
// await view.save();
// }
// }
// const updateViewName = view.options.updateViewName;
// if (updateViewName) {
// const v = await View.findOne({
// where: {
// name: updateViewName,
// collection_name: view.collection_name
// },
// });
// if (v) {
// view.options.updateViewId = v.id;
// view.setDataValue('options', view.options);
// view.changed('options', true);
// await view.save();
// }
// }
// console.log({detailsViewName, updateViewName});
// }
// for (let table of tables) {
// const options = table.getOptions();

View File

@ -4,28 +4,29 @@ import ViewFactory from '@/components/views';
export function Create(props) {
console.log(props);
const { title, viewCollectionName, viewName } = props.schema;
const { title, viewName, collection_name } = props.schema;
const { activeTab = {}, item = {} } = props;
const { association, collection_name } = activeTab;
const { association } = activeTab;
const params = {};
if (association) {
params['resourceName'] = association;
params['associatedName'] = collection_name;
params['associatedName'] = activeTab.collection_name;
params['associatedKey'] = item.itemId;
} else {
params['resourceName'] = collection_name;
params['resourceKey'] = item.itemId;
}
console.log(params);
const drawerRef = useRef<any>();
return (
<>
<ViewFactory
{...props}
reference={drawerRef}
viewCollectionName={viewCollectionName}
viewName={viewName}
{...params}
/>

View File

@ -4,15 +4,15 @@ import ViewFactory from '@/components/views';
export function Update(props) {
console.log(props);
const { title, viewCollectionName, viewName } = props.schema;
const { title, viewName, resourceName, collection_name } = props.schema;
const { activeTab = {}, item = {} } = props;
const { association, collection_name } = activeTab;
const { association } = activeTab;
const params = {};
if (association) {
params['resourceName'] = association;
params['associatedName'] = collection_name;
params['associatedName'] = resourceName;
params['associatedKey'] = item.itemId;
} else {
params['resourceName'] = collection_name;
@ -22,15 +22,15 @@ export function Update(props) {
const drawerRef = useRef<any>();
return (
<>
<ViewFactory
<ViewFactory
{...props}
reference={drawerRef}
viewCollectionName={viewCollectionName}
viewName={viewName}
{...params}
/>
<Button type={'primary'} onClick={() => {
drawerRef.current.setVisible(true);
drawerRef.current.getData(item.itemId);
}}>{title}</Button>
</>
)

View File

@ -29,8 +29,8 @@ export function CollectionIndex(props) {
// }
/>
<div className={'collection-content'}>
<ViewFactory {...props}
viewCollectionName={collection}
<ViewFactory
{...props}
viewName={viewName||defaultViewName}
resourceName={collection}
/>

View File

@ -4,6 +4,7 @@ import { CollectionTabPane } from './CollectionTabPane';
import { getPathName, redirectTo } from './utils';
import api from '@/api-client';
import { useRequest } from 'umi';
import { Spin } from '@nocobase/client';
export function CollectionSingle(props) {
console.log(props);
@ -11,13 +12,16 @@ export function CollectionSingle(props) {
const { tabs = [] } = props.collection;
const activeTab = tabs.find(tab => tab.name == item.tabName)||{};
console.log(activeTab);
const { data = {} } = useRequest(() => activeTab && api.resource(activeTab.collection_name).getPageInfo({
const { data = {}, loading } = useRequest(() => activeTab && api.resource(activeTab.collection_name).getPageInfo({
resourceKey: item.itemId,
}));
console.log(data);
if (!activeTab) {
return null;
}
if (loading) {
return <Spin/>;
}
return (
<div>
<PageHeader

View File

@ -5,7 +5,7 @@ import { useRequest, request, Spin } from '@nocobase/client';
export function CollectionTabPane(props) {
const { pageInfo = {}, activeTab = {}, item = {} } = props;
const { viewCollectionName, viewName, association, collection_name, field = {} } = activeTab;
const { viewName, association, collection_name, field = {} } = activeTab;
const { sourceKey = 'id' } = field;
const params = {};
@ -25,7 +25,6 @@ export function CollectionTabPane(props) {
<div>
<ViewFactory
{...props}
viewCollectionName={viewCollectionName}
viewName={viewName}
{...params}
/>

View File

@ -34,7 +34,7 @@ export function CollectionLoader(props: any) {
return (
<div className={'collection'}>
<div className={'collection-index'}>
<CollectionIndex collection={data.data||{}} {...props}/>
{ items.length === 0 && <CollectionIndex collection={data.data||{}} {...props}/> }
</div>
{items.length > 0 && (
<div className={'collection-item'}>

View File

@ -0,0 +1,20 @@
import React from 'react';
import { PageHeader } from 'antd';
import './style.less';
export function Page(props: any) {
const { children, ...restProps } = props;
return (
<div>
<PageHeader
ghost={false}
{...restProps}
/>
<div className={'page-content'}>
{children}
</div>
</div>
);
};
export default Page;

View File

@ -4,3 +4,4 @@ export { CollectionLoader } from './CollectionLoader';
export { PageLoader as default } from './PageLoader';
export { SideMenuLayout } from './SideMenuLayout/index';
export { TopMenuLayout } from './TopMenuLayout/index';
export { Page } from './Page';

View File

@ -0,0 +1,3 @@
.page-content {
margin: 24px;
}

View File

@ -0,0 +1,39 @@
import React from 'react';
import { Card, Descriptions, Button } from 'antd';
import { Actions } from '@/components/actions';
import api from '@/api-client';
import { useRequest } from 'umi';
import { Spin } from '@nocobase/client';
export function Details(props: any) {
const {
activeTab = {},
pageInfo = {},
schema,
resourceName,
associatedName,
associatedKey,
resourceKey,
} = props;
const { data = {}, loading } = useRequest(() => {
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
return api.resource(name).get({
resourceKey,
associatedKey,
});
});
console.log(props);
const { actions = [], fields = [] } = props.schema;
return (
<Card bordered={false}>
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
{loading ? <Spin/> : (
<Descriptions bordered column={1}>
{fields.map((field: any) => (
<Descriptions.Item label={field.title||field.name}>{data[field.name]}</Descriptions.Item>
))}
</Descriptions>
)}
</Card>
);
}

View File

@ -14,15 +14,37 @@ import {
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 const DrawerForm = forwardRef((props: any, ref) => {
console.log(props);
const {
activeTab = {},
pageInfo = {},
schema,
resourceName,
associatedName,
associatedKey,
} = props;
const [visible, setVisible] = useState(false);
const { data, run, loading } = useRequest((resourceKey) => {
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
return api.resource(name).get({
resourceKey,
associatedKey,
});
}, {
manual: true,
});
useImperativeHandle(ref, () => ({
setVisible,
getData: run,
}));
const actions = createAsyncFormActions();
const { title } = props.schema||{};
const { title, fields: properties ={} } = props.schema||{};
console.log({properties});
return (
<Drawer
{...props}
@ -39,37 +61,33 @@ export const DrawerForm = forwardRef((props: any, ref) => {
}}></Button>
]}
>
<SchemaForm
colon={true}
layout={'vertical'}
initialValues={{}}
actions={actions}
schema={{
type: 'object',
properties: {
title: {
type: 'string',
title: '标题',
required: true,
{loading ? <Spin/> : (
<SchemaForm
colon={true}
layout={'vertical'}
initialValues={data}
actions={actions}
schema={{
type: 'object',
properties,
}}
expressionScope={{
text(...args: any[]) {
return React.createElement('span', {}, ...args)
},
},
}}
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>
tooltip(title: string, offset = 3) {
return (
<Tooltip title={title}>
<QuestionCircleOutlined
style={{ margin: '0 3px', cursor: 'default', marginLeft: offset }}
/>
</Tooltip>
);
},
}}
>
</SchemaForm>
)}
</Drawer>
);
});

View File

@ -1,38 +1,11 @@
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { Table as AntdTable, Card } from 'antd';
import { redirectTo } from '@/components/pages/CollectionLoader/utils';
import { Actions } from '@/components/actions';
import ViewFactory from '@/components/views';
import { request, useRequest } from 'umi';
import { useRequest } from 'umi';
import api from '@/api-client';
const dataSource = [];
for (let i = 0; i < 46; i++) {
dataSource.push({
id: i,
name: `Edward King ${i}`,
age: 32,
address: `London, Park Lane no. ${i}`,
});
}
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
},
];
import get from 'lodash/get';
import { components, fields2columns } from './SortableTable';
export interface SimpleTableProps {
schema?: any;
@ -53,23 +26,46 @@ export function SimpleTable(props: SimpleTableProps) {
associatedName,
associatedKey,
} = props;
const { fields, viewCollectionName, rowViewName, actions = [] } = schema;
const { rowKey = 'id', fields = [], rowViewName, actions = [] } = schema;
const { sourceKey = 'id' } = activeTab.field||{};
const drawerRef = useRef<any>();
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
const { data } = useRequest(() => api.resource(name).list({
const { data, mutate } = useRequest(() => api.resource(name).list({
associatedKey,
}));
console.log(activeTab);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const onChange = (selectedRowKeys: React.ReactText[]) => {
setSelectedRowKeys(selectedRowKeys);
}
const tableProps: any = {};
if (actions.length) {
tableProps.rowSelection = {
selectedRowKeys,
onChange,
}
}
return (
<Card bordered={false}>
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
<ViewFactory reference={drawerRef} viewCollectionName={viewCollectionName} viewName={rowViewName}/>
<AntdTable dataSource={data} onRow={(data) => ({
onClick: () => {
drawerRef.current.setVisible(true);
},
})} columns={fields} />
<ViewFactory
{...props}
viewName={rowViewName}
reference={drawerRef}
/>
<AntdTable
columns={fields2columns(fields)}
dataSource={data}
rowKey={rowKey}
components={components({data, mutate})}
onRow={(record) => ({
onClick: () => {
drawerRef.current.setVisible(true);
drawerRef.current.getData(record[rowKey]);
}
})}
{...tableProps}
/>
</Card>
);
}

View File

@ -0,0 +1,54 @@
import React from 'react';
// @ts-ignore
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import { MenuOutlined } from '@ant-design/icons';
import arrayMove from 'array-move';
import findIndex from 'lodash/findIndex';
import get from 'lodash/get';
import './style.less';
export const SortableItem = sortableElement(props => <tr {...props} />);
export const SortableContainer = sortableContainer(props => <tbody {...props} />);
export const DragHandle = sortableHandle(() => (
<MenuOutlined style={{ cursor: 'pointer', color: '#999' }} />
));
export const components = ({data, mutate}) => {
return {
body: {
wrapper: props => (
<SortableContainer
useDragHandle
helperClass="row-dragging"
onSortEnd={({ oldIndex, newIndex }) => {
if (oldIndex !== newIndex) {
const newData = arrayMove([].concat(data), oldIndex, newIndex).filter(el => !!el);
mutate(newData);
}
}}
{...props}
/>
),
row: ({ className, style, ...restProps }) => {
// function findIndex base on Table rowKey props and should always be a right array index
const index = findIndex(data, (x: any) => x.id === restProps['data-row-key']);
return <SortableItem index={index} {...restProps} />;
},
},
};
};
export function fields2columns(fields) {
const columns: any[] = fields.map(field => {
const type = get(field, 'component.type');
if (type === 'sort') {
field.render = () => <DragHandle/>;
}
return {
...field,
...(field.component||{}),
}
});
return columns;
}

View File

@ -0,0 +1,14 @@
.row-dragging {
background: #fafafa;
border: 1px solid #ccc;
z-index: 99999;
}
.row-dragging td {
padding: 16px;
visibility: hidden;
}
.row-dragging .drag-visible {
visibility: visible;
}

View File

@ -1,9 +1,10 @@
import React from 'react';
import React, { useState } from 'react';
import { Table as AntdTable, Card } from 'antd';
import { redirectTo } from '@/components/pages/CollectionLoader/utils';
import { Actions } from '@/components/actions';
import { request, useRequest } from 'umi';
import api from '@/api-client';
import { components, fields2columns } from './SortableTable';
const columns = [
{
@ -40,28 +41,45 @@ export function Table(props: TableProps) {
associatedName,
associatedKey,
} = props;
const { defaultTabId, fields, defaultTabName, rowKey = 'id', actions = [] } = schema;
const { fields, defaultTabName, rowKey = 'id', actions = [] } = schema;
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
const { data } = useRequest(() => api.resource(name).list({
const { data, mutate } = useRequest(() => api.resource(name).list({
associatedKey,
}));
const { sourceKey = 'id' } = activeTab.field||{};
console.log(data);
console.log(activeTab);
console.log(props);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const onChange = (selectedRowKeys: React.ReactText[]) => {
setSelectedRowKeys(selectedRowKeys);
}
const tableProps: any = {};
if (actions.length) {
tableProps.rowSelection = {
selectedRowKeys,
onChange,
}
}
return (
<Card bordered={false}>
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
<AntdTable dataSource={data} onRow={(data) => ({
onClick: () => {
redirectTo({
...props.match.params,
[activeTab ? 'newItem' : 'lastItem']: {
itemId: data[rowKey]||data.id,
tabName: defaultTabName,
},
});
},
})} columns={fields} />
<AntdTable
dataSource={data}
rowKey={rowKey}
columns={fields2columns(fields)}
components={components({data, mutate})}
onRow={(data) => ({
onClick: () => {
redirectTo({
...props.match.params,
[activeTab ? 'newItem' : 'lastItem']: {
itemId: data[rowKey]||data.id,
tabName: defaultTabName,
},
});
},
})}
{...tableProps}
/>
</Card>
);
}

View File

@ -1,38 +0,0 @@
import React from 'react';
import { Card, Descriptions, Button } from 'antd';
import { Actions } from '@/components/actions';
export function Details(props: any) {
console.log(props);
const { actions = [], fields = [] } = props.schema;
return (
<Card bordered={false}>
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
<Descriptions bordered column={1}>
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
<Descriptions.Item label="Billing Mode">Prepaid</Descriptions.Item>
<Descriptions.Item label="Automatic Renewal">YES</Descriptions.Item>
<Descriptions.Item label="Order time">2018-04-24 18:00:00</Descriptions.Item>
<Descriptions.Item label="Usage Time" >
2019-04-24 18:00:00
</Descriptions.Item>
<Descriptions.Item label="Negotiated Amount">$80.00</Descriptions.Item>
<Descriptions.Item label="Discount">$20.00</Descriptions.Item>
<Descriptions.Item label="Official Receipts">$60.00</Descriptions.Item>
<Descriptions.Item label="Config Info">
Data disk type: MongoDB
<br />
Database version: 3.4
<br />
Package: dds.mongo.mid
<br />
Storage space: 10 GB
<br />
Replication factor: 3
<br />
Region: East China 1<br />
</Descriptions.Item>
</Descriptions>
</Card>
);
}

View File

@ -1,11 +1,12 @@
import React from 'react';
import { Form, DrawerForm } from './Form/index';
import { Table } from './Table';
import { Details } from './Details';
import { useRequest, request, Spin } from '@nocobase/client';
import { SimpleTable } from './SimpleTable';
import api from '@/api-client';
import { useRequest } from 'umi';
import { Spin } from '@nocobase/client';
import { SimpleTable } from './SimpleTable';
import { Table } from './Table';
import { Form, DrawerForm } from './Form/index';
import { Details } from './Details';
const TEMPLATES = new Map<string, any>();
@ -24,19 +25,37 @@ registerView('Table', Table);
registerView('SimpleTable', SimpleTable);
registerView('Details', Details);
export default function ViewFactory(props) {
const { activeTab, viewCollectionName, viewName, reference } = props;
console.log({viewCollectionName, viewName});
const { data = {}, error, loading, run } = useRequest(() => api.resource(viewCollectionName).getView({
resourceKey: viewName,
}), {
refreshDeps: [viewCollectionName, viewName],
export interface ViewProps {
resourceName: string;
resourceKey?: string | number;
associatedName?: string;
associatedKey?: string | number;
viewName?: string;
[key: string]: any;
}
export default function ViewFactory(props: ViewProps) {
const {
associatedName,
associatedKey,
resourceName,
viewName,
reference,
} = props;
const { data = {}, loading } = useRequest(() => {
const params = {
resourceKey: viewName,
associatedName: associatedName,
};
return api.resource(resourceName).getView(params);
}, {
refreshDeps: [associatedName, resourceName, viewName],
});
console.log(activeTab);
console.log(data);
if (loading) {
return <Spin/>;
}
const { template } = data.data;
const { template } = data;
const Template = getViewTemplate(template);
return Template && <Template {...props} ref={reference} schema={data.data}/>;
return Template && <Template {...props} ref={reference} schema={data}/>;
}

View File

@ -1,11 +1,38 @@
import React from 'react';
import { Link } from 'umi';
import { Statistic, Card, Row, Col } from 'antd';
import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
import { Page } from '@/components/pages';
export default (props: any) => {
return (
<div>
Page1
<Link to={'/page2'}>Page2</Link>
</div>
<Page title={'页面标题'}>
<Row gutter={16}>
<Col span={12}>
<Card>
<Statistic
title="Active"
value={11.28}
precision={2}
valueStyle={{ color: '#3f8600' }}
prefix={<ArrowUpOutlined />}
suffix="%"
/>
</Card>
</Col>
<Col span={12}>
<Card>
<Statistic
title="Idle"
value={9.3}
precision={2}
valueStyle={{ color: '#cf1322' }}
prefix={<ArrowDownOutlined />}
suffix="%"
/>
</Card>
</Col>
</Row>
</Page>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import { Link } from 'umi';
import { Page } from '@/components/pages';
export default (props: any) => {
return (
<div>
<Page title={'页面标题'}>
Page2
<Link to={'/page1'}>Page1</Link>
</div>
</Page>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import { Link } from 'umi';
import { Page } from '@/components/pages';
export default (props: any) => {
return (
<div>
<Page title={'页面标题'}>
Page3
<Link to={'/page1'}>Page1</Link>
</div>
</Page>
);
};

View File

@ -1,11 +1,12 @@
import React from 'react';
import { Link } from 'umi';
import { Page } from '@/components/pages';
export default (props: any) => {
return (
<div>
<Page title={'页面标题'}>
Page4
<Link to={'/page1'}>Page1</Link>
</div>
</Page>
);
};

View File

@ -4,10 +4,24 @@ export default {
name: 'actions',
title: '操作配置',
fields: [
{
type: 'integer',
name: 'sort',
title: '排序',
component: {
type: 'sort',
className: 'drag-visible',
width: 60,
},
},
{
type: 'string',
name: 'type',
title: '类型',
component: {
type: 'string',
className: 'drag-visible',
},
},
{
type: 'string',

View File

@ -6,6 +6,16 @@ export default {
title: '数据表配置',
model: CollectionModel,
fields: [
{
type: 'integer',
name: 'sort',
title: '排序',
component: {
type: 'sort',
className: 'drag-visible',
width: 60,
},
},
{
type: 'string',
name: 'title',
@ -14,6 +24,7 @@ export default {
required: true,
component: {
type: 'string',
className: 'drag-visible',
},
},
{
@ -48,21 +59,41 @@ export default {
type: 'hasMany',
name: 'fields',
sourceKey: 'name',
actions: {
list: {
sort: 'sort',
},
},
},
{
type: 'hasMany',
name: 'actions',
sourceKey: 'name',
actions: {
list: {
sort: 'sort',
},
},
},
{
type: 'hasMany',
name: 'tabs',
sourceKey: 'name',
actions: {
list: {
sort: 'sort',
},
},
},
{
type: 'hasMany',
name: 'views',
sourceKey: 'name',
actions: {
list: {
sort: 'sort',
},
},
},
],
actions: [

View File

@ -4,10 +4,24 @@ export default {
name: 'fields',
title: '字段配置',
fields: [
{
type: 'integer',
name: 'sort',
title: '排序',
component: {
type: 'sort',
className: 'drag-visible',
width: 60,
},
},
{
type: 'string',
name: 'type',
title: '类型',
component: {
type: 'string',
className: 'drag-visible',
},
},
{
type: 'string',
@ -19,6 +33,16 @@ export default {
name: 'title',
title: '名称',
},
{
type: 'boolean',
name: 'showInListAction',
title: '显示在表格里',
},
{
type: 'boolean',
name: 'showInGetAction',
title: '显示在详情里',
},
{
type: 'json',
name: 'options',
@ -26,6 +50,7 @@ export default {
{
type: 'belongsTo',
name: 'collection',
title: '所属数据表',
target: 'collections',
targetKey: 'name',
},

View File

@ -4,10 +4,24 @@ export default {
name: 'tabs',
title: '标签配置',
fields: [
{
type: 'integer',
name: 'sort',
title: '排序',
component: {
type: 'sort',
className: 'drag-visible',
width: 60,
},
},
{
type: 'string',
name: 'type',
title: '类型',
component: {
type: 'string',
className: 'drag-visible',
},
},
{
type: 'string',

View File

@ -4,10 +4,24 @@ export default {
name: 'views',
title: '视图配置',
fields: [
{
type: 'integer',
name: 'sort',
title: '排序',
component: {
type: 'sort',
className: 'drag-visible',
width: 60,
},
},
{
type: 'string',
name: 'type',
title: '类型',
component: {
type: 'string',
className: 'drag-visible',
},
},
{
type: 'string',

View File

@ -20,7 +20,9 @@ export default async (ctx, next) => {
});
collection.setDataValue('defaultViewId', get(views, [0, 'id']));
collection.setDataValue('defaultViewName', get(views, [0, 'name']));
const tabs = await collection.getTabs() as Model[];
const tabs = await collection.getTabs({
order: [['sort', 'asc']],
}) as Model[];
const tabItems = [];
for (const tab of tabs) {
const itemTab = {

View File

@ -34,7 +34,9 @@ function pages2routes(pages: Array<any>) {
export default async function getRoutes(ctx, next) {
const database: Database = ctx.database;
const Page = database.getModel('pages');
let pages = await Page.findAll({});
let pages = await Page.findAll({
order: [['sort', 'asc']],
});
const data = flatToTree(pages.map(row => row.toJSON()), {
id: 'id',
parentId: 'parent_id',

View File

@ -15,15 +15,14 @@ const transforms = {
return arr;
},
form: async (fields: Model[]) => {
const arr = [];
const schema = {};
for (const field of fields) {
arr.push({
...field.toJSON(),
...field.options,
dataIndex: field.name,
});
schema[field.name] = {
type: 'string',
title: field.title||field.name,
};
}
return arr;
return schema;
},
details: async (fields: Model[]) => {
const arr = [];
@ -50,8 +49,16 @@ export default async (ctx, next) => {
// },
}));
const collection = await view.getCollection();
const fields = await collection.getFields();
const actions = await collection.getActions();
const fields = await collection.getFields({
order: [
['sort', 'asc'],
]
});
const actions = await collection.getActions({
order: [
['sort', 'asc'],
]
});
const actionNames = view.options.actionNames||[];
console.log(view.options);
if (view.type === 'table') {

View File

@ -6,10 +6,12 @@ export default {
fields: [
{
type: 'integer',
name: 'parent_id',
title: '父级页面',
name: 'sort',
title: '排序',
component: {
type: 'number',
type: 'sort',
className: 'drag-visible',
width: 60,
},
},
{
@ -20,6 +22,15 @@ export default {
isMainTitle: true,
component: {
type: 'string',
className: 'drag-visible',
},
},
{
type: 'integer',
name: 'parent_id',
title: '父级页面',
component: {
type: 'number',
},
},
{
@ -140,10 +151,6 @@ export default {
type: 'boolean',
},
},
{
type: 'integer',
name: 'order',
},
{
type: 'hasMany',
name: 'children',

View File

@ -7,6 +7,7 @@ export default {
{
type: 'string',
name: 'username',
title: '用户名',
},
],
actions: [

View File

@ -44,6 +44,7 @@ export function middleware(options: MiddlewareOptions = {}) {
const field = table.getField(names[0]) as BelongsTo | HasMany | BelongsToMany | HasOne;
if (names.length == 0 || field) {
let resourceType = 'single';
let actions = {};
if (field) {
if (field instanceof HasOne) {
resourceType = 'hasOne';
@ -54,10 +55,14 @@ export function middleware(options: MiddlewareOptions = {}) {
} else if (field instanceof BelongsToMany) {
resourceType = 'belongsToMany';
}
if (field.options.actions) {
actions = field.options.actions;
}
}
resourcer.define({
type: resourceType as any,
name: resourceName,
actions,
});
}
}