feat: improve plugins (#14)

* feat: delete defined resources

* feat: api client

* feat: view fields & tab field options & page info...

* fix: view type
This commit is contained in:
chenos 2020-11-13 22:01:14 +08:00 committed by GitHub
parent 92160d0fe5
commit 9daae13c68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 235 additions and 135 deletions

View File

@ -0,0 +1,56 @@
import { request } from 'umi';
interface ResourceProxyConstructor {
new <T, H extends object>(target: T, handler: ProxyHandler<H>): H
}
const ResourceProxy = Proxy as ResourceProxyConstructor;
interface Params {
resourceKey?: string | number;
// resourceName?: string;
// associatedName?: string;
associatedKey?: string | number;
fields?: any;
filter?: any;
}
interface Handler {
[name: string]: (params?: Params) => Promise<any>;
}
class APIClient {
resource(name: string) {
return new ResourceProxy<object, Handler>({}, {
get(target, method, receiver) {
return (params: Params = {}) => {
console.log(params);
const { associatedKey, resourceKey, ...restParams } = params;
let url = `/${name}`;
let options: any = {};
if (['list', 'get'].indexOf(method as string) !== -1) {
options.method = 'get';
options.params = restParams;
} else {
options.method = 'post';
options.data = restParams;
}
if (associatedKey) {
url = `/${name.split('.').join(`/${associatedKey}/`)}`;
}
url += `:${method as string}`;
// console.log(name, name.split('.'), associatedKey, name.split('.').join(`/${associatedKey}/`));
if (resourceKey) {
url += `/${resourceKey}`;
}
console.log({url, params});
return request(url, options);
};
}
});
}
}
const api = new APIClient();
export default api;

View File

@ -2,7 +2,8 @@ import Api from '../../../server/src';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import path from 'path'; import path from 'path';
import Database, { Model } from '@nocobase/database'; import Database, { Model } from '@nocobase/database';
import { get } from 'lodash'; import actions from '../../../actions/src';
import associated from '../../../actions/src/middlewares/associated';
const sync = { const sync = {
force: true, force: true,
@ -34,6 +35,9 @@ const api = Api.create({
}, },
}); });
api.resourcer.use(associated);
api.resourcer.registerHandlers(actions.associate);
const data = { const data = {
title: '后台应用', title: '后台应用',
path: '/', path: '/',

View File

@ -2,12 +2,19 @@ import React from 'react';
import { PageHeader, Tabs, Button, Statistic, Descriptions } from 'antd'; import { PageHeader, Tabs, Button, Statistic, Descriptions } from 'antd';
import { CollectionTabPane } from './CollectionTabPane'; import { CollectionTabPane } from './CollectionTabPane';
import { getPathName, redirectTo } from './utils'; import { getPathName, redirectTo } from './utils';
import api from '@/api-client';
import { useRequest } from 'umi';
export function CollectionSingle(props) { export function CollectionSingle(props) {
console.log(props); console.log(props);
const { item = {} } = props; const { item = {} } = props;
const { tabs = [] } = props.collection; const { tabs = [] } = props.collection;
const activeTab = tabs.find(tab => tab.name == item.tabName)||{}; const activeTab = tabs.find(tab => tab.name == item.tabName)||{};
console.log(activeTab);
const { data = {} } = useRequest(() => activeTab && api.resource(activeTab.collection_name).getPageInfo({
resourceKey: item.itemId,
}));
console.log(data);
if (!activeTab) { if (!activeTab) {
return null; return null;
} }
@ -21,7 +28,7 @@ export function CollectionSingle(props) {
removeLastItem: true, removeLastItem: true,
}); });
}} }}
title={'企业信息库'} title={data.pageTitle}
// subTitle="This is a subtitle" // subTitle="This is a subtitle"
extra={[ extra={[
// <Button key="3">Operation</Button>, // <Button key="3">Operation</Button>,
@ -47,7 +54,7 @@ export function CollectionSingle(props) {
} }
/> />
<div className={'collection-content'}> <div className={'collection-content'}>
<CollectionTabPane {...props} activeTab={activeTab}/> <CollectionTabPane {...props} pageInfo={data} activeTab={activeTab}/>
</div> </div>
</div> </div>
); );

View File

@ -4,23 +4,27 @@ import { PageHeader, Tabs, Button, Statistic, Descriptions } from 'antd';
import { useRequest, request, Spin } from '@nocobase/client'; import { useRequest, request, Spin } from '@nocobase/client';
export function CollectionTabPane(props) { export function CollectionTabPane(props) {
const { activeTab = {}, item = {} } = props; const { pageInfo = {}, activeTab = {}, item = {} } = props;
const { viewCollectionName, viewName, association, collection_name } = activeTab; const { viewCollectionName, viewName, association, collection_name, field = {} } = activeTab;
const { sourceKey = 'id' } = field;
const params = {}; const params = {};
if (association) { if (association) {
params['resourceName'] = association; params['resourceName'] = association;
params['associatedName'] = collection_name; params['associatedName'] = collection_name;
params['associatedKey'] = item.itemId; params['associatedKey'] = pageInfo[sourceKey] || item.itemId;
} else { } else {
params['resourceName'] = collection_name; params['resourceName'] = collection_name;
params['resourceKey'] = item.itemId; params['resourceKey'] = item.itemId;
} }
console.log(activeTab);
return ( return (
<div> <div>
<ViewFactory {...props} <ViewFactory
{...props}
viewCollectionName={viewCollectionName} viewCollectionName={viewCollectionName}
viewName={viewName} viewName={viewName}
{...params} {...params}

View File

@ -4,6 +4,7 @@ import CollectionIndex from './CollectionIndex';
import CollectionSingle from './CollectionSingle'; import CollectionSingle from './CollectionSingle';
import './style.less'; import './style.less';
import { useRequest, request, Spin } from '@nocobase/client'; import { useRequest, request, Spin } from '@nocobase/client';
import api from '@/api-client';
export function CollectionLoader(props: any) { export function CollectionLoader(props: any) {
let { path, pagepath, collection } = props.match.params; let { path, pagepath, collection } = props.match.params;
@ -24,7 +25,7 @@ export function CollectionLoader(props: any) {
})); }));
props.match.params['items'] = items; props.match.params['items'] = items;
console.log(props.match, path); console.log(props.match, path);
const { data = {}, error, loading, run } = useRequest(() => request(`/${collection}:getCollection`)); const { data = {}, error, loading, run } = useRequest(() => api.resource(collection).getCollection());
if (loading) { if (loading) {
return <Spin/>; return <Spin/>;

View File

@ -3,10 +3,11 @@ import React, { useState } from 'react';
import { TemplateLoader } from './TemplateLoader'; import { TemplateLoader } from './TemplateLoader';
import { useRequest, request } from '@nocobase/client'; import { useRequest, request } from '@nocobase/client';
import templates from '@/templates'; import templates from '@/templates';
import api from '@/api-client';
export function PageLoader(props: any) { export function PageLoader(props: any) {
const { path } = props.match.params; const { path } = props.match.params;
const { data = {}, error, loading, run } = useRequest(() => request('/pages:getRoutes')); const { data = {}, error, loading, run } = useRequest(() => api.resource('pages').getRoutes());
const [first, setFirst] = useState(true); const [first, setFirst] = useState(true);
(window as any).routesReload = async () => { (window as any).routesReload = async () => {
setFirst(false); setFirst(false);

View File

@ -1,8 +1,10 @@
import React, { useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { Table as AntdTable, Card } from 'antd'; import { Table as AntdTable, Card } from 'antd';
import { redirectTo } from '@/components/pages/CollectionLoader/utils'; import { redirectTo } from '@/components/pages/CollectionLoader/utils';
import { Actions } from '@/components/actions'; import { Actions } from '@/components/actions';
import ViewFactory from '@/components/views'; import ViewFactory from '@/components/views';
import { request, useRequest } from 'umi';
import api from '@/api-client';
const dataSource = []; const dataSource = [];
for (let i = 0; i < 46; i++) { for (let i = 0; i < 46; i++) {
@ -32,20 +34,42 @@ const columns = [
}, },
]; ];
export function SimpleTable(props: any) { export interface SimpleTableProps {
schema?: any;
activeTab?: any;
resourceName: string;
associatedName?: string;
associatedKey?: string;
[key: string]: any;
}
export function SimpleTable(props: SimpleTableProps) {
console.log(props); console.log(props);
const { activeTab, schema } = props; const {
const { viewCollectionName, rowViewName, actions = [] } = schema; activeTab = {},
pageInfo = {},
schema,
resourceName,
associatedName,
associatedKey,
} = props;
const { fields, viewCollectionName, rowViewName, actions = [] } = schema;
const { sourceKey = 'id' } = activeTab.field||{};
const drawerRef = useRef<any>(); const drawerRef = useRef<any>();
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
const { data } = useRequest(() => api.resource(name).list({
associatedKey,
}));
console.log(activeTab);
return ( return (
<Card bordered={false}> <Card bordered={false}>
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/> <Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
<ViewFactory reference={drawerRef} viewCollectionName={viewCollectionName} viewName={rowViewName}/> <ViewFactory reference={drawerRef} viewCollectionName={viewCollectionName} viewName={rowViewName}/>
<AntdTable dataSource={dataSource} onRow={(data) => ({ <AntdTable dataSource={data} onRow={(data) => ({
onClick: () => { onClick: () => {
drawerRef.current.setVisible(true); drawerRef.current.setVisible(true);
}, },
})} columns={columns} /> })} columns={fields} />
</Card> </Card>
); );
} }

View File

@ -5,6 +5,7 @@ import { Table } from './Table';
import { Details } from './Details'; import { Details } from './Details';
import { useRequest, request, Spin } from '@nocobase/client'; import { useRequest, request, Spin } from '@nocobase/client';
import { SimpleTable } from './SimpleTable'; import { SimpleTable } from './SimpleTable';
import api from '@/api-client';
const TEMPLATES = new Map<string, any>(); const TEMPLATES = new Map<string, any>();
@ -24,10 +25,14 @@ registerView('SimpleTable', SimpleTable);
registerView('Details', Details); registerView('Details', Details);
export default function ViewFactory(props) { export default function ViewFactory(props) {
const { viewCollectionName, viewName, reference } = props; const { activeTab, viewCollectionName, viewName, reference } = props;
const { data = {}, error, loading, run } = useRequest(() => request(`/${viewCollectionName}:getView/${viewName}`), { console.log({viewCollectionName, viewName});
const { data = {}, error, loading, run } = useRequest(() => api.resource(viewCollectionName).getView({
resourceKey: viewName,
}), {
refreshDeps: [viewCollectionName, viewName], refreshDeps: [viewCollectionName, viewName],
}); });
console.log(activeTab);
if (loading) { if (loading) {
return <Spin/>; return <Spin/>;
} }

View File

@ -3,16 +3,7 @@ import { Table as AntdTable, Card } from 'antd';
import { redirectTo } from '@/components/pages/CollectionLoader/utils'; import { redirectTo } from '@/components/pages/CollectionLoader/utils';
import { Actions } from '@/components/actions'; import { Actions } from '@/components/actions';
import { request, useRequest } from 'umi'; import { request, 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 = [ const columns = [
{ {
@ -32,12 +23,31 @@ const columns = [
}, },
]; ];
export function Table(props: any) { export interface TableProps {
console.log(props); schema?: any;
const { activeTab, schema } = props; activeTab?: any;
const { defaultTabId, defaultTabName, actions = [] } = schema; resourceName: string;
const { data } = useRequest(() => request('/collections')); associatedName?: string;
associatedKey?: string;
[key: string]: any;
}
export function Table(props: TableProps) {
const {
activeTab = {},
schema,
resourceName,
associatedName,
associatedKey,
} = props;
const { defaultTabId, fields, defaultTabName, rowKey = 'id', actions = [] } = schema;
const name = associatedName ? `${associatedName}.${resourceName}` : resourceName;
const { data } = useRequest(() => api.resource(name).list({
associatedKey,
}));
const { sourceKey = 'id' } = activeTab.field||{};
console.log(data); console.log(data);
console.log(activeTab);
return ( return (
<Card bordered={false}> <Card bordered={false}>
<Actions {...props} style={{ marginBottom: 14 }} actions={actions}/> <Actions {...props} style={{ marginBottom: 14 }} actions={actions}/>
@ -46,12 +56,12 @@ export function Table(props: any) {
redirectTo({ redirectTo({
...props.match.params, ...props.match.params,
[activeTab ? 'newItem' : 'lastItem']: { [activeTab ? 'newItem' : 'lastItem']: {
itemId: data.id, itemId: data[rowKey]||data.id,
tabName: defaultTabName, tabName: defaultTabName,
}, },
}); });
}, },
})} columns={columns} /> })} columns={fields} />
</Card> </Card>
); );
} }

View File

@ -69,7 +69,7 @@ export default {
actionNames: ['update'], actionNames: ['update'],
}, },
{ {
type: 'simple', type: 'table',
name: 'simple', name: 'simple',
title: '简易模式', title: '简易模式',
template: 'SimpleTable', template: 'SimpleTable',

View File

@ -104,7 +104,7 @@ export default {
actionNames: ['update'], actionNames: ['update'],
}, },
{ {
type: 'simple', type: 'table',
name: 'simple', name: 'simple',
title: '简易模式', title: '简易模式',
template: 'SimpleTable', template: 'SimpleTable',

View File

@ -69,7 +69,7 @@ export default {
actionNames: ['update'], actionNames: ['update'],
}, },
{ {
type: 'simple', type: 'table',
name: 'simple', name: 'simple',
title: '简易模式', title: '简易模式',
template: 'SimpleTable', template: 'SimpleTable',

View File

@ -75,7 +75,7 @@ export default {
actionNames: ['update'], actionNames: ['update'],
}, },
{ {
type: 'simple', type: 'table',
name: 'simple', name: 'simple',
title: '简易模式', title: '简易模式',
template: 'SimpleTable', template: 'SimpleTable',

View File

@ -88,7 +88,7 @@ export default {
actionNames: ['update'], actionNames: ['update'],
}, },
{ {
type: 'simple', type: 'table',
name: 'simple', name: 'simple',
title: '简易模式', title: '简易模式',
template: 'SimpleTable', template: 'SimpleTable',

View File

@ -1,5 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
export default {
name: 'actions',
} as ResourceOptions;

View File

@ -1,25 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
export default {
name: 'collections',
actions: {
list: {
fields: {
appends: ['fields'],
}
},
get: {
fields: {
appends: ['fields'],
}
},
// get: {
// handler: async (ctx, next) => {
// ctx.body = {
// get: {}
// }
// await next();
// }
// },
},
} as ResourceOptions;

View File

@ -1,5 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
export default {
name: 'fields',
} as ResourceOptions;

View File

@ -1,5 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
export default {
name: 'tabs',
} as ResourceOptions;

View File

@ -1,5 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
export default {
name: 'views',
} as ResourceOptions;

View File

@ -9,8 +9,4 @@ export default async function (this: any, options = {}) {
database.import({ database.import({
directory: path.resolve(__dirname, 'collections'), directory: path.resolve(__dirname, 'collections'),
}); });
resourcer.import({
directory: path.resolve(__dirname, 'resources'),
});
} }

View File

@ -9,8 +9,4 @@ export default async function (options = {}) {
database.import({ database.import({
directory: path.resolve(__dirname, 'collections'), directory: path.resolve(__dirname, 'collections'),
}); });
resourcer.import({
directory: path.resolve(__dirname, 'resources'),
});
} }

View File

@ -4,7 +4,7 @@ import { get } from 'lodash';
export default async (ctx, next) => { export default async (ctx, next) => {
const { resourceName, resourceKey } = ctx.action.params; const { resourceName, resourceKey } = ctx.action.params;
const [Collection, Tab, View] = ctx.db.getModels(['collections', 'tabs', 'views']) as ModelCtor<Model>[]; const [Collection, Field, Tab, View] = ctx.db.getModels(['collections', 'fields', 'tabs', 'views']) as ModelCtor<Model>[];
const collection = await Collection.findOne(Collection.parseApiJson({ const collection = await Collection.findOne(Collection.parseApiJson({
filter: { filter: {
name: resourceName, name: resourceName,
@ -20,14 +20,33 @@ export default async (ctx, next) => {
}); });
collection.setDataValue('defaultViewId', get(views, [0, 'id'])); collection.setDataValue('defaultViewId', get(views, [0, 'id']));
collection.setDataValue('defaultViewName', get(views, [0, 'name'])); collection.setDataValue('defaultViewName', get(views, [0, 'name']));
const tabs = await collection.getTabs(); const tabs = await collection.getTabs() as Model[];
ctx.body = { const tabItems = [];
...collection.toJSON(), for (const tab of tabs) {
tabs: tabs.map(tab => ({ const itemTab = {
...tab.toJSON(), ...tab.toJSON(),
...tab.options, ...tab.options,
viewCollectionName: tab.type == 'association' ? tab.options.association : tab.collection_name, };
})), if (itemTab.type == 'association') {
const field = await Field.findOne({
where: {
collection_name: itemTab.collection_name,
name: itemTab.association,
},
});
itemTab.field = field ? {
...field.toJSON(),
...field.options,
} : {};
itemTab.viewCollectionName = itemTab.association;
} else {
itemTab.viewCollectionName = itemTab.collection_name;
}
tabItems.push(itemTab);
}
ctx.body = {
...collection.toJSON(),
tabs: tabItems,
}; };
await next(); await next();
} }

View File

@ -0,0 +1,14 @@
import { ResourceOptions } from '@nocobase/resourcer';
import { Model, ModelCtor } from '@nocobase/database';
import { get } from 'lodash';
export default async (ctx, next) => {
const { resourceName, resourceKey } = ctx.action.params;
const M = ctx.db.getModel(resourceName) as ModelCtor<Model>;
const model = await M.findByPk(resourceKey);
ctx.body = {
pageTitle: model.title,
...model.toJSON(),
};
await next();
};

View File

@ -2,6 +2,41 @@ import { ResourceOptions } from '@nocobase/resourcer';
import { Model, ModelCtor } from '@nocobase/database'; import { Model, ModelCtor } from '@nocobase/database';
import { get } from 'lodash'; import { get } from 'lodash';
const transforms = {
table: async (fields: Model[]) => {
const arr = [];
for (const field of fields) {
arr.push({
...field.toJSON(),
...field.options,
dataIndex: field.name,
});
}
return arr;
},
form: async (fields: Model[]) => {
const arr = [];
for (const field of fields) {
arr.push({
...field.toJSON(),
...field.options,
dataIndex: field.name,
});
}
return arr;
},
details: async (fields: Model[]) => {
const arr = [];
for (const field of fields) {
arr.push({
...field.toJSON(),
...field.options,
});
}
return arr;
},
};
export default async (ctx, next) => { export default async (ctx, next) => {
const { resourceName, resourceKey } = ctx.action.params; const { resourceName, resourceKey } = ctx.action.params;
const [View, Field, Action] = ctx.db.getModels(['views', 'fields', 'actions']) as ModelCtor<Model>[]; const [View, Field, Action] = ctx.db.getModels(['views', 'fields', 'actions']) as ModelCtor<Model>[];
@ -10,9 +45,9 @@ export default async (ctx, next) => {
collection_name: resourceName, collection_name: resourceName,
name: resourceKey, name: resourceKey,
}, },
fields: { // fields: {
appends: ['actions', 'fields'], // appends: ['actions', 'fields'],
}, // },
})); }));
const collection = await view.getCollection(); const collection = await view.getCollection();
const fields = await collection.getFields(); const fields = await collection.getFields();
@ -27,14 +62,14 @@ export default async (ctx, next) => {
}); });
view.setDataValue('defaultTabName', get(defaultTabs, [0, 'name'])); view.setDataValue('defaultTabName', get(defaultTabs, [0, 'name']));
} }
if (view.options.updateViewId) { if (view.options.updateViewName) {
view.setDataValue('rowViewName', view.options.updateViewName); view.setDataValue('rowViewName', view.options.updateViewName);
} }
view.setDataValue('viewCollectionName', view.collection_name); view.setDataValue('viewCollectionName', view.collection_name);
ctx.body = { ctx.body = {
...view.toJSON(), ...view.toJSON(),
...(view.options||{}), ...(view.options||{}),
fields, fields: await (transforms[view.type]||transforms.table)(fields),
actions: actions.filter(action => actionNames.includes(action.name)).map(action => ({ actions: actions.filter(action => actionNames.includes(action.name)).map(action => ({
...action.toJSON(), ...action.toJSON(),
...action.options, ...action.options,

View File

@ -1,9 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
import getRoutes from '../actions/getRoutes';
export default {
name: 'pages',
actions: {
getRoutes,
},
} as ResourceOptions;

View File

@ -3,6 +3,8 @@ import Database from '@nocobase/database';
import Resourcer from '@nocobase/resourcer'; import Resourcer from '@nocobase/resourcer';
import getCollection from './actions/getCollection'; import getCollection from './actions/getCollection';
import getView from './actions/getView'; import getView from './actions/getView';
import getRoutes from './actions/getRoutes';
import getPageInfo from './actions/getPageInfo';
export default async function (options = {}) { export default async function (options = {}) {
const database: Database = this.database; const database: Database = this.database;
@ -14,8 +16,6 @@ export default async function (options = {}) {
resourcer.registerHandler('getCollection', getCollection); resourcer.registerHandler('getCollection', getCollection);
resourcer.registerHandler('getView', getView); resourcer.registerHandler('getView', getView);
resourcer.registerHandler('getPageInfo', getPageInfo);
resourcer.import({ resourcer.registerHandler('pages:getRoutes', getRoutes);
directory: path.resolve(__dirname, 'resources'),
});
} }

View File

@ -1,5 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
export default {
name: 'roles',
} as ResourceOptions;

View File

@ -9,8 +9,4 @@ export default async function (options = {}) {
database.import({ database.import({
directory: path.resolve(__dirname, 'collections'), directory: path.resolve(__dirname, 'collections'),
}); });
resourcer.import({
directory: path.resolve(__dirname, 'resources'),
});
} }

View File

@ -1,5 +0,0 @@
import { ResourceOptions } from '@nocobase/resourcer';
export default {
name: 'users',
} as ResourceOptions;

View File

@ -9,8 +9,4 @@ export default async function (options = {}) {
database.import({ database.import({
directory: path.resolve(__dirname, 'collections'), directory: path.resolve(__dirname, 'collections'),
}); });
resourcer.import({
directory: path.resolve(__dirname, 'resources'),
});
} }