feat: actions & views (#18)
* feat: 视图新增支持 associated 的情况 * fix: 解决关系数据的视图问题 * 表单数据初始化 * feat: sortable table * 排序 * 表格细节调整 * feat: field actions
This commit is contained in:
		
							parent
							
								
									b0ba1472bb
								
							
						
					
					
						commit
						eb93f0734f
					
				| @ -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" | ||||
|  | ||||
| @ -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();
 | ||||
|  | ||||
| @ -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} | ||||
|       /> | ||||
|  | ||||
| @ -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; | ||||
| @ -25,12 +25,12 @@ export function Update(props) { | ||||
|       <ViewFactory  | ||||
|         {...props} | ||||
|         reference={drawerRef} | ||||
|         viewCollectionName={viewCollectionName} | ||||
|         viewName={viewName} | ||||
|         {...params} | ||||
|       /> | ||||
|       <Button type={'primary'} onClick={() => { | ||||
|         drawerRef.current.setVisible(true); | ||||
|         drawerRef.current.getData(item.itemId); | ||||
|       }}>{title}</Button> | ||||
|     </> | ||||
|   ) | ||||
|  | ||||
| @ -29,8 +29,8 @@ export function CollectionIndex(props) { | ||||
|         // }
 | ||||
|       /> | ||||
|       <div className={'collection-content'}> | ||||
|         <ViewFactory {...props}  | ||||
|           viewCollectionName={collection}  | ||||
|         <ViewFactory | ||||
|           {...props} | ||||
|           viewName={viewName||defaultViewName} | ||||
|           resourceName={collection} | ||||
|         /> | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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} | ||||
|       /> | ||||
|  | ||||
| @ -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'}> | ||||
|  | ||||
							
								
								
									
										20
									
								
								packages/app/src/components/pages/Page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/app/src/components/pages/Page.tsx
									
									
									
									
									
										Normal 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; | ||||
| @ -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'; | ||||
|  | ||||
							
								
								
									
										3
									
								
								packages/app/src/components/pages/style.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/app/src/components/pages/style.less
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| .page-content { | ||||
|   margin: 24px; | ||||
| } | ||||
							
								
								
									
										39
									
								
								packages/app/src/components/views/Details.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								packages/app/src/components/views/Details.tsx
									
									
									
									
									
										Normal 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> | ||||
|   ); | ||||
| } | ||||
| @ -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> | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| @ -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> | ||||
|   ); | ||||
| } | ||||
|  | ||||
							
								
								
									
										54
									
								
								packages/app/src/components/views/SortableTable/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								packages/app/src/components/views/SortableTable/index.tsx
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
							
								
								
									
										14
									
								
								packages/app/src/components/views/SortableTable/style.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/app/src/components/views/SortableTable/style.less
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
| @ -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> | ||||
|   ); | ||||
| } | ||||
| @ -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> | ||||
|   ); | ||||
| } | ||||
| @ -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}/>; | ||||
| } | ||||
|  | ||||
| @ -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> | ||||
|   ); | ||||
| }; | ||||
| @ -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> | ||||
|   ); | ||||
| }; | ||||
| @ -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> | ||||
|   ); | ||||
| }; | ||||
| @ -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> | ||||
|   ); | ||||
| }; | ||||
| @ -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', | ||||
|  | ||||
| @ -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: [ | ||||
|  | ||||
| @ -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', | ||||
|     }, | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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 = { | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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') { | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -7,6 +7,7 @@ export default { | ||||
|     { | ||||
|       type: 'string', | ||||
|       name: 'username', | ||||
|       title: '用户名', | ||||
|     }, | ||||
|   ], | ||||
|   actions: [ | ||||
|  | ||||
| @ -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, | ||||
|             }); | ||||
|           } | ||||
|         } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user