updates
This commit is contained in:
parent
af02a895c3
commit
6f54ee3d4e
@ -11,25 +11,92 @@ nav:
|
||||
|
||||
NocoBase 核心主要围绕三点:
|
||||
|
||||
- 数据的存储 —— 结构和关系
|
||||
- 数据的行为 —— 操作和事件
|
||||
- 数据的形态 —— 页面和区块
|
||||
- 数据的结构
|
||||
- 数据的行为
|
||||
- 数据的形态
|
||||
|
||||
由此构建了
|
||||
由此抽象了三类配置协议
|
||||
|
||||
- @nocobase/database:提供灵活且强大的数据库构造器
|
||||
- @nocobase/resourcer:为数据提供资源操作方法
|
||||
- @nocobase/blocks:为数据提供的 UI 模块
|
||||
- Collection:用于描述数据的结构和关系
|
||||
- Resourcer:用于描述数据资源和操作方法
|
||||
- UI Schema:用于描述用户界面(组件树结构)
|
||||
|
||||
更近一步
|
||||
## Collection
|
||||
|
||||
- @nocobase/database 和 @nocobase/resourcer 组装成了 @nocobase/server,也就是最核心的 NocoBase
|
||||
- @nocobase/blocks 是前端最重要部分,提供了现代化的 Block-styled Editor/Renderer
|
||||
基于 Sequelize ModelOptions
|
||||
|
||||
<img style="width:700px;" src="./nocobase-core.png"/>
|
||||
```ts
|
||||
{
|
||||
name: 'posts',
|
||||
fields: [
|
||||
{type: 'string', name: 'title'},
|
||||
{type: 'text', name: 'content'},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
## 数据的存储 —— 结构和关系
|
||||
## Resourcer
|
||||
|
||||
## 数据的行为 —— 操作和事件
|
||||
基于资源(resource)和操作方法(action)设计,将 REST 和 RPC 思想融合起来
|
||||
|
||||
## 数据的形态 —— 页面和区块
|
||||
```ts
|
||||
{
|
||||
name: 'posts',
|
||||
actions: {
|
||||
list: {
|
||||
filter: {}, // 过滤
|
||||
fields: [], // 输出哪些字段
|
||||
sort: '-created_at', // 排序
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
// ...
|
||||
},
|
||||
get: {
|
||||
filter: {},
|
||||
fields: [],
|
||||
// ...
|
||||
},
|
||||
create: {
|
||||
fields: [],
|
||||
values: {},
|
||||
// ...
|
||||
},
|
||||
update: {
|
||||
fields: [],
|
||||
values: {},
|
||||
// ...
|
||||
},
|
||||
destroy: {
|
||||
filter: {},
|
||||
// ...
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## UI Schema
|
||||
|
||||
基于 Formily Schema 2.0
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'object',
|
||||
// 'x-component': 'Form',
|
||||
properties: {
|
||||
title: {
|
||||
type: 'string',
|
||||
title: '标题',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
title: '标题',
|
||||
'x-component': 'Input.TextArea',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
更进一步,构建了整个 NocoBase 架构:
|
||||
|
||||
<img src="https://nocobase.oss-cn-beijing.aliyuncs.com/60ffd9aff98871c643569219c2873749.png" style="max-width: 800px;width:100%"/>
|
@ -1,74 +0,0 @@
|
||||
---
|
||||
title: '@nocobase/blocks'
|
||||
order: 6
|
||||
---
|
||||
|
||||
# @nocobase/blocks <Badge>未实现</Badge>
|
||||
|
||||
<Alert title="注意" type="warning">
|
||||
|
||||
因为前端代码的打包还有许多细节问题,核心代码暂时也都放在 @nocobase/app 里了。
|
||||
|
||||
v0.4 版本 components 分为了 Action、Field、View 三部分,下一把版本打算都整合到 Block 里。
|
||||
|
||||
</Alert>
|
||||
|
||||
在 NocoBase 中,区块是所有 HTML 元素片段(包括 React/Vue 等框架的自定义元素)的总称。区块可以任意组合或嵌套,为了方便使用,内置了常用的一些区块,大致分类有:
|
||||
|
||||
- Database - 数据
|
||||
- Fields - 字段
|
||||
- Buttons - 按钮
|
||||
- Media - 多媒体
|
||||
- Design - 设计
|
||||
- 自定义区块
|
||||
|
||||
## Database - 数据
|
||||
|
||||
数据类型的区块需要绑定数据源。
|
||||
|
||||
### table
|
||||
### form
|
||||
### descriptions
|
||||
### calendar
|
||||
### kanban
|
||||
|
||||
## Fields - 字段
|
||||
|
||||
字段是一种特殊的子区块,只用于数据类型区块中,有两种形态:静态纯展示和动态可输入。
|
||||
|
||||
### boolean
|
||||
### checkbox
|
||||
### cascader
|
||||
### time
|
||||
### markdown
|
||||
### string
|
||||
### icon
|
||||
### textarea
|
||||
### number
|
||||
### remoteSelect
|
||||
### drawerSelect
|
||||
### colorSelect
|
||||
### subTable
|
||||
### icon
|
||||
|
||||
## Buttons - 按钮
|
||||
|
||||
### create
|
||||
### update
|
||||
### destroy
|
||||
### filter
|
||||
### print
|
||||
### export
|
||||
### button
|
||||
|
||||
## Media - 多媒体
|
||||
|
||||
### markdown
|
||||
|
||||
## Design - 设计
|
||||
|
||||
### grid
|
||||
### drawer
|
||||
### page
|
||||
|
||||
## 自定义区块
|
@ -1,256 +0,0 @@
|
||||
---
|
||||
title: '@nocobase/client'
|
||||
order: 6
|
||||
---
|
||||
|
||||
# @nocobase/client <Badge>未实现</Badge>
|
||||
|
||||
## 介绍
|
||||
|
||||
提供适配 Ant Design 组件的 NocoBase 客户端。
|
||||
|
||||
<Alert title="注意">
|
||||
|
||||
@nocobase/client 可以用于任意 React 框架中,不过还存在许多难点和细节未解决。
|
||||
|
||||
</Alert>
|
||||
|
||||
## Loaders
|
||||
|
||||
### TemplateLoader <Badge>待完善</Badge>
|
||||
|
||||
- routes:路由表,大杂烩。类型包括:layout、page、redirect、url、menuGroup,**页面就是 type=page 的 route**
|
||||
- templates:模板
|
||||
- pathname:URL 路径
|
||||
|
||||
为了适应无代码需求,提供了一种 URL 和 Template 映射规则,如以下例子:
|
||||
|
||||
```ts
|
||||
const routes = [
|
||||
{
|
||||
type: 'layout',
|
||||
name: 'auth', // 因为 /login、/register 没有统一的前缀,所以这里没有配置 path
|
||||
template: 'AuthLayout', // 用于处理 login、register 等页面的布局
|
||||
children: [
|
||||
{
|
||||
type: 'page',
|
||||
name: 'login',
|
||||
path: '/login',
|
||||
template: 'BlockLoader', // 用于解析 blocks 配置参数
|
||||
blocks: [],
|
||||
},
|
||||
{
|
||||
type: 'page',
|
||||
name: 'register',
|
||||
path: '/register',
|
||||
template: 'BlockLoader',
|
||||
blocks: [],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'layout',
|
||||
name: 'admin',
|
||||
path: '/admin', // /admin 下的任意 uri 都转到这里
|
||||
template: 'AdminLoader',
|
||||
redirect: '/admin/welcome',
|
||||
// admin layout 提供了 top/left 布局的菜单,菜单由 children 组成
|
||||
// 通过解析 /admin/:name,找到对应子页面
|
||||
// 「菜单和页面配置」就是这部分的内容
|
||||
children: [
|
||||
{
|
||||
type: 'page',
|
||||
name: 'welcome',
|
||||
template: 'BlockLoader',
|
||||
title: '欢迎',
|
||||
blocks: [],
|
||||
},
|
||||
{
|
||||
type: 'url',
|
||||
url: 'https://www.nocobase.com/',
|
||||
title: 'xxx',
|
||||
},
|
||||
{
|
||||
type: 'menuGroup',
|
||||
title: 'xxx',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// 配置跳转
|
||||
type: 'redirect',
|
||||
path: '/',
|
||||
redirect: '/admin',
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
<pre lang="tsx">
|
||||
<TemplateLoader
|
||||
pathname={pathname}
|
||||
routes={routes}
|
||||
templates={{
|
||||
AuthLayout,
|
||||
BlockLoader,
|
||||
AdminLoader,
|
||||
}}
|
||||
/>
|
||||
</pre>
|
||||
|
||||
### BlockLoader <Badge type="error">未实现</Badge>
|
||||
|
||||
区块驱动器。
|
||||
|
||||
### AdminLoader <Badge>待完善</Badge>
|
||||
|
||||
一种 top/left 菜单结构的 Admin 布局。菜单由其对应的 children 组成,通过 `/admin/:name` 映射到对应子页面,「菜单和页面配置」就是这部分的内容。
|
||||
|
||||
### ShareLoader <Badge type="error">未实现</Badge>
|
||||
|
||||
分享模块,细节待补充
|
||||
|
||||
## Blocks
|
||||
|
||||
将页面内部的各个块元素进行提炼,抽象了 block(区块)的概念。
|
||||
|
||||
### Grid - 布局
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'grid',
|
||||
span: 12,
|
||||
blocks: [
|
||||
{
|
||||
col: 1,
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
col: 2,
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
col: 1,
|
||||
order: 2,
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Descriptions - 详情
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'descriptions',
|
||||
fields: [],
|
||||
actions: [],
|
||||
}
|
||||
```
|
||||
|
||||
### Form - 表单
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'form',
|
||||
fields: [],
|
||||
// 表单提交反馈信息,细节待定
|
||||
returnType,
|
||||
redirect,
|
||||
message,
|
||||
}
|
||||
```
|
||||
|
||||
### Table - 表单
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'table',
|
||||
defaultPerPage: 20,
|
||||
draggable: false,
|
||||
filter: {},
|
||||
sort: [],
|
||||
detailsOpenMode: 'drawer',
|
||||
actions: [],
|
||||
fields: [],
|
||||
details: [],
|
||||
labelField,
|
||||
}
|
||||
```
|
||||
|
||||
### Calendar - 日历
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'calendar',
|
||||
filter: {},
|
||||
detailsOpenMode: 'drawer',
|
||||
actions: [],
|
||||
details: [],
|
||||
labelField,
|
||||
}
|
||||
```
|
||||
|
||||
### Kanban - 看板
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'kanban',
|
||||
groupField,
|
||||
labelField,
|
||||
fields,
|
||||
filter,
|
||||
actions,
|
||||
detailsOpenMode,
|
||||
details,
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown
|
||||
|
||||
```ts
|
||||
{
|
||||
type: 'markdown',
|
||||
content: '',
|
||||
}
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
操作按钮
|
||||
|
||||
### create - 新增
|
||||
|
||||
### update - 编辑
|
||||
|
||||
### destroy - 删除
|
||||
|
||||
### filter - 筛选
|
||||
|
||||
### print - 打印
|
||||
|
||||
### export - 导出
|
||||
|
||||
## Fields
|
||||
|
||||
字段控件
|
||||
|
||||
### boolean
|
||||
### cascader
|
||||
### checkbox
|
||||
### checkboxes
|
||||
### colorSelect
|
||||
### date
|
||||
### drawerSelect
|
||||
### filter
|
||||
### icon
|
||||
### markdown
|
||||
### number
|
||||
### password
|
||||
### percent
|
||||
### radio
|
||||
### remoteSelect
|
||||
### string
|
||||
### select
|
||||
### subTable
|
||||
### textarea
|
||||
### time
|
||||
### upload
|
@ -13,7 +13,7 @@ NocoBase 是一个开源免费的无代码、低代码开发平台。 无论是
|
||||
|
||||
## 架构
|
||||
|
||||
<img src="./nocobase.png" style="max-width: 800px;"/>
|
||||
<img src="https://nocobase.oss-cn-beijing.aliyuncs.com/60ffd9aff98871c643569219c2873749.png" style="max-width: 800px;width:100%"/>
|
||||
|
||||
### 微内核
|
||||
|
||||
@ -41,11 +41,3 @@ NocoBase 采用微内核架构,框架只保留核心的概念,具体各类
|
||||
- 直接写在代码里,多用于处理动态配置
|
||||
- 保存在文件里,多用于系统表配置或纯开发配置
|
||||
- 保存在数据表里,多用于业务表配置
|
||||
|
||||
## 核心点在哪里?
|
||||
|
||||
NocoBase 本质上是对数据的信息化处理,包括三点核心:
|
||||
|
||||
- 数据的存储 —— 结构和关系
|
||||
- 数据的行为 —— 方法和事件
|
||||
- 数据的形态 —— 页面和区块
|
||||
|
6
nodemon.json
Normal file
6
nodemon.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"watch": ["packages/", ".env"],
|
||||
"ignore": ["packages/app"],
|
||||
"ext": "ts",
|
||||
"exec": "ts-node -r dotenv/config ./packages/api/src/index.ts"
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
"scripts": {
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"clean": "lerna clean",
|
||||
"start-server": "nodemon",
|
||||
"start-docs": "dumi dev",
|
||||
"build-docs": "dumi build",
|
||||
"build2": "lerna run build",
|
||||
@ -44,6 +45,7 @@
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"lerna": "^3.22.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nodemon": "^2.0.12",
|
||||
"pg": "^8.6.0",
|
||||
"pg-hstore": "^2.3.3",
|
||||
"prettier": "^2.3.0",
|
||||
|
@ -19,7 +19,7 @@
|
||||
"@nocobase/plugin-file-manager": "^0.4.0-alpha.7",
|
||||
"@nocobase/plugin-pages": "^0.4.0-alpha.7",
|
||||
"@nocobase/plugin-permissions": "^0.4.0-alpha.7",
|
||||
"@nocobase/plugin-routes": "^0.4.0-alpha.7",
|
||||
"@nocobase/plugin-ui-router": "^0.4.0-alpha.7",
|
||||
"@nocobase/plugin-ui-schema": "^0.4.0-alpha.7",
|
||||
"@nocobase/plugin-users": "^0.4.0-alpha.7",
|
||||
"@nocobase/server": "^0.4.0-alpha.7",
|
||||
|
@ -39,7 +39,7 @@ const api = Api.create({
|
||||
|
||||
const plugins = [
|
||||
'@nocobase/plugin-collections',
|
||||
'@nocobase/plugin-routes',
|
||||
'@nocobase/plugin-ui-router',
|
||||
'@nocobase/plugin-ui-schema',
|
||||
// '@nocobase/plugin-action-logs',
|
||||
// '@nocobase/plugin-pages',
|
||||
|
@ -27,25 +27,27 @@ import * as uiSchema from './ui-schema';
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'AdminLayout',
|
||||
title: `后台`,
|
||||
uiSchema: uiSchema.menu,
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
component: 'AuthLayout',
|
||||
children: [
|
||||
{
|
||||
name: 'login',
|
||||
type: 'route',
|
||||
path: '/login',
|
||||
component: 'DefaultPage',
|
||||
component: 'RouteSchemaRenderer',
|
||||
title: `登录`,
|
||||
uiSchema: uiSchema.login,
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
type: 'route',
|
||||
path: '/register',
|
||||
component: 'DefaultPage',
|
||||
component: 'RouteSchemaRenderer',
|
||||
title: `注册`,
|
||||
uiSchema: uiSchema.register,
|
||||
},
|
||||
|
@ -2,7 +2,6 @@ import { ISchema } from '@formily/react';
|
||||
|
||||
export const login: ISchema = {
|
||||
key: 'dtf9j0b8p9u',
|
||||
name: 'dtf9j0b8p9u',
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: {
|
||||
|
@ -1,6 +1,5 @@
|
||||
export const register = {
|
||||
key: '46qlxqam3xk',
|
||||
name: '46qlxqam3xk',
|
||||
type: 'object',
|
||||
properties: {
|
||||
username: {
|
||||
|
@ -1,127 +0,0 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { MemoryRouter as Router, useParams, Link } from 'react-router-dom';
|
||||
import { RouteSwitch } from '../';
|
||||
|
||||
const components = {
|
||||
Index: () => <div>index</div>,
|
||||
Home: ({ children }) => (
|
||||
<div>
|
||||
<h1>Home</h1>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
Blog: ({ children }) => (
|
||||
<div>
|
||||
<h1>Blog</h1>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
BlogPost: () => {
|
||||
let { slug } = useParams<any>();
|
||||
return <div>Now showing post {slug}</div>;
|
||||
},
|
||||
Login: () => <div>login</div>,
|
||||
Register: () => <div>register</div>,
|
||||
};
|
||||
|
||||
const routes = [
|
||||
{
|
||||
type: 'redirect',
|
||||
from: '/blog/123',
|
||||
to: '/blog/1234',
|
||||
},
|
||||
// {
|
||||
// component: 'Home',
|
||||
// routes: [
|
||||
// {
|
||||
// path: '/blog/123555',
|
||||
// component: () => <div>/blog/123555</div>,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/blog',
|
||||
component: 'Blog',
|
||||
routes: [
|
||||
{
|
||||
path: '/blog/:slug',
|
||||
// exact: true,
|
||||
component: 'BlogPost',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: 'Home',
|
||||
routes: [
|
||||
// {
|
||||
// path: '/blog/123555',
|
||||
// component: () => <div>/blog/123555</div>,
|
||||
// },
|
||||
{
|
||||
path: '/login',
|
||||
component: 'Login',
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: 'Register',
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
// exact: true,
|
||||
component: 'Index',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
it('route component', () => {
|
||||
const t = renderer
|
||||
.create(
|
||||
<Router initialEntries={['/']}>
|
||||
<RouteSwitch
|
||||
routes={[
|
||||
{
|
||||
path: '/',
|
||||
component: () => <div>test</div>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Router>,
|
||||
)
|
||||
.toJSON();
|
||||
expect(t).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('pathname=/', () => {
|
||||
const t = renderer
|
||||
.create(
|
||||
<Router initialEntries={['/']}>
|
||||
<RouteSwitch routes={routes} components={components} />
|
||||
</Router>,
|
||||
)
|
||||
.toJSON();
|
||||
expect(t).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('pathname=/login', () => {
|
||||
const t = renderer
|
||||
.create(
|
||||
<Router initialEntries={['/login']}>
|
||||
<RouteSwitch routes={routes} components={components} />
|
||||
</Router>,
|
||||
)
|
||||
.toJSON();
|
||||
expect(t).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('pathname=/blog/123', () => {
|
||||
const t = renderer
|
||||
.create(
|
||||
<Router initialEntries={['/blog/123']}>
|
||||
<RouteSwitch routes={routes} components={components} />
|
||||
</Router>,
|
||||
)
|
||||
.toJSON();
|
||||
expect(t).toMatchSnapshot();
|
||||
});
|
@ -1,41 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`pathname=/ 1`] = `
|
||||
<div>
|
||||
<h1>
|
||||
Home
|
||||
</h1>
|
||||
<div>
|
||||
index
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`pathname=/blog/123 1`] = `
|
||||
<div>
|
||||
<h1>
|
||||
Blog
|
||||
</h1>
|
||||
<div>
|
||||
Now showing post
|
||||
1234
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`pathname=/login 1`] = `
|
||||
<div>
|
||||
<h1>
|
||||
Home
|
||||
</h1>
|
||||
<div>
|
||||
login
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`route component 1`] = `
|
||||
<div>
|
||||
test
|
||||
</div>
|
||||
`;
|
@ -1,73 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Link,
|
||||
useLocation,
|
||||
useRouteMatch,
|
||||
MemoryRouter as Router,
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
RouteSwitch,
|
||||
AuthLayout,
|
||||
AdminLayout,
|
||||
PageTemplate,
|
||||
} from '@nocobase/client';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/admin/:slug(.+)?',
|
||||
component: 'AdminLayout',
|
||||
},
|
||||
{
|
||||
component: 'AuthLayout',
|
||||
routes: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login',
|
||||
component: 'PageTemplate',
|
||||
title: '登录',
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
path: '/register',
|
||||
component: 'PageTemplate',
|
||||
title: '注册',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'redirect',
|
||||
from: '/',
|
||||
to: '/admin',
|
||||
exact: true,
|
||||
},
|
||||
];
|
||||
|
||||
const components = {
|
||||
AuthLayout,
|
||||
AdminLayout,
|
||||
PageTemplate,
|
||||
};
|
||||
|
||||
function App() {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<div>
|
||||
<div>{location.pathname}</div>
|
||||
<ul>
|
||||
<li><Link to={'/login'}>path=/login</Link></li>
|
||||
<li><Link to={'/register'}>path=/register</Link></li>
|
||||
<li><Link to={'/'}>path=/</Link></li>
|
||||
<li><Link to={'/admin/welcome'}>path=/admin/welcome</Link></li>
|
||||
</ul>
|
||||
<RouteSwitch routes={routes} components={components} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<Router initialEntries={['/login']}>
|
||||
<App />
|
||||
</Router>
|
||||
);
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
title: RouteSwitch - 路由转换器
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 3
|
||||
title: 其他
|
||||
path: /client/others
|
||||
---
|
||||
|
||||
# RouteSwitch - 路由转换器
|
||||
|
||||
## 代码演示
|
||||
|
||||
<code src="./demos/demo1.tsx"/>
|
@ -1,64 +0,0 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { Switch, Route as ReactRoute, Redirect } from 'react-router-dom';
|
||||
|
||||
export const RouteComponentContext = createContext({});
|
||||
|
||||
export interface RouteSwitchProps {
|
||||
routes?: any[];
|
||||
components?: any;
|
||||
}
|
||||
|
||||
export function RouteSwitch(props: RouteSwitchProps) {
|
||||
const { routes } = props;
|
||||
if (!Array.isArray(routes)) {
|
||||
return null;
|
||||
}
|
||||
const components = props.components || useContext(RouteComponentContext);
|
||||
return (
|
||||
<RouteComponentContext.Provider value={components}>
|
||||
<Switch>
|
||||
{routes.map((route, key) => {
|
||||
if (route.type === 'redirect') {
|
||||
return (
|
||||
<Redirect
|
||||
key={key}
|
||||
exact={route.exact}
|
||||
from={route.from}
|
||||
to={route.to}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (route.component) {
|
||||
let path = route.path;
|
||||
if (!path && Array.isArray(route.routes)) {
|
||||
path = route.routes.map((r) => r.path);
|
||||
}
|
||||
return (
|
||||
<ReactRoute
|
||||
key={key}
|
||||
path={path}
|
||||
exact={route.exact}
|
||||
component={(routeProps) => {
|
||||
const Component =
|
||||
typeof route.component === 'string'
|
||||
? components[route.component]
|
||||
: route.component;
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Component {...routeProps} route={route}>
|
||||
<RouteSwitch routes={route.routes} />
|
||||
</Component>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Switch>
|
||||
</RouteComponentContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default RouteSwitch;
|
14
packages/client/src/components/admin-layout/index.md
Normal file
14
packages/client/src/components/admin-layout/index.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: AdminLayout - 后台布局
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Templates
|
||||
path: /client/templates
|
||||
---
|
||||
|
||||
# AdminLayout - 后台布局
|
||||
|
||||
内置的后台布局模板,提供了基础的菜单和路由切换。
|
92
packages/client/src/components/admin-layout/index.tsx
Normal file
92
packages/client/src/components/admin-layout/index.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Spin,
|
||||
Layout,
|
||||
PageHeader,
|
||||
Modal,
|
||||
Menu,
|
||||
Collapse,
|
||||
Dropdown,
|
||||
} from 'antd';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import {
|
||||
Link,
|
||||
useLocation,
|
||||
useRouteMatch,
|
||||
useHistory,
|
||||
Redirect,
|
||||
} from 'react-router-dom';
|
||||
import { SchemaRenderer } from '../../schemas';
|
||||
import { useRequest } from 'ahooks';
|
||||
import './style.less';
|
||||
|
||||
function LayoutWithMenu({ schema }) {
|
||||
const match = useRouteMatch<any>();
|
||||
const location = useLocation();
|
||||
const sideMenuRef = useRef();
|
||||
const [activeKey, setActiveKey] = useState(match.params.name);
|
||||
const onSelect = (info) => {
|
||||
console.log('LayoutWithMenu', schema);
|
||||
setActiveKey(info.key);
|
||||
};
|
||||
console.log({ match });
|
||||
return (
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<SchemaRenderer
|
||||
schema={schema}
|
||||
scope={{
|
||||
sideMenuRef,
|
||||
onSelect,
|
||||
selectedKeys: [activeKey].filter(Boolean),
|
||||
}}
|
||||
/>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider
|
||||
ref={sideMenuRef}
|
||||
theme={'light'}
|
||||
width={200}
|
||||
></Layout.Sider>
|
||||
<Layout.Content>
|
||||
{activeKey && <Content activeKey={activeKey} />}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
function Content({ activeKey }) {
|
||||
const { data = {}, loading } = useRequest(
|
||||
`ui_schemas:getTree/${activeKey}?filter[parentId]=${activeKey}`,
|
||||
{
|
||||
refreshDeps: [activeKey],
|
||||
formatResult: (result) => result?.data,
|
||||
},
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
return <SchemaRenderer schema={data} />;
|
||||
}
|
||||
|
||||
export function AdminLayout({ route }: any) {
|
||||
const { data = {}, loading } = useRequest(
|
||||
`ui_schemas:getTree/${route.uiSchemaKey}`,
|
||||
{
|
||||
refreshDeps: [route],
|
||||
formatResult: (result) => result?.data,
|
||||
},
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
|
||||
return <LayoutWithMenu schema={data} />;
|
||||
}
|
||||
|
||||
export default AdminLayout;
|
49
packages/client/src/components/admin-layout/style.less
Normal file
49
packages/client/src/components/admin-layout/style.less
Normal file
@ -0,0 +1,49 @@
|
||||
// .fields-collapse {
|
||||
// border: 1px solid #f0f0f0;
|
||||
// border-bottom: 0;
|
||||
// > .ant-collapse-item {
|
||||
// border-bottom: 1px solid #f0f0f0;
|
||||
// }
|
||||
// .ant-collapse-content {
|
||||
// &.ant-collapse-content-active {
|
||||
// border-top: 1px solid #f0f0f0;
|
||||
// }
|
||||
// }
|
||||
// > .ant-collapse-item > .ant-collapse-content {
|
||||
// background: #fff;
|
||||
// }
|
||||
// > .ant-collapse-item > .ant-collapse-content > .ant-collapse-content-box {
|
||||
// padding: 16px;
|
||||
// padding-bottom: 1px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.ant-collapse {
|
||||
border: 1px solid #d9d9d9 !important;
|
||||
border-bottom: 0 !important;
|
||||
> .ant-collapse-item {
|
||||
border-bottom: 1px solid #d9d9d9 !important;
|
||||
}
|
||||
.ant-collapse-content {
|
||||
border-top: 1px solid #d9d9d9 !important;
|
||||
}
|
||||
.ant-collapse-content > .ant-collapse-content-box {
|
||||
padding: 24px !important;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.database-sider {
|
||||
background: #fafafa;
|
||||
padding-top: 16px;
|
||||
.ant-menu {
|
||||
background: #fafafa;
|
||||
border-right: 0;
|
||||
}
|
||||
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||
background: #fff;
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
14
packages/client/src/components/auth-layout/index.md
Normal file
14
packages/client/src/components/auth-layout/index.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
title: AuthLayout - 登录布局
|
||||
nav:
|
||||
title: 组件
|
||||
path: /client
|
||||
group:
|
||||
order: 2
|
||||
title: Templates
|
||||
path: /client/templates
|
||||
---
|
||||
|
||||
# AuthLayout - 登录布局
|
||||
|
||||
内置的登录、注册页布局
|
15
packages/client/src/components/auth-layout/index.tsx
Normal file
15
packages/client/src/components/auth-layout/index.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { useLocation, useHistory } from 'react-router-dom';
|
||||
|
||||
export function AuthLayout({ children, route }: any) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
return (
|
||||
<div style={{ maxWidth: 320, margin: '0 auto' }}>
|
||||
<h1>NocoBase</h1>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AuthLayout;
|
@ -0,0 +1,25 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { SchemaRenderer } from '../../schemas';
|
||||
|
||||
export function RouteSchemaRenderer({ route }) {
|
||||
const { data = {}, loading } = useRequest(
|
||||
`ui_schemas:getTree/${route.uiSchemaKey}`,
|
||||
{
|
||||
refreshDeps: [route],
|
||||
formatResult: (result) => result?.data,
|
||||
},
|
||||
);
|
||||
if (loading) {
|
||||
return <Spin />;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<SchemaRenderer schema={data} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RouteSchemaRenderer;
|
49
packages/client/src/components/router-config/demos/demo1.tsx
Normal file
49
packages/client/src/components/router-config/demos/demo1.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
Link,
|
||||
useLocation,
|
||||
useRouteMatch,
|
||||
MemoryRouter as Router,
|
||||
} from 'react-router-dom';
|
||||
import { createRouteSwitch, RouteRedirectProps } from '..';
|
||||
|
||||
const RouteSwitch = createRouteSwitch({
|
||||
components: {
|
||||
Home: (props) => {
|
||||
console.log({ props });
|
||||
return <div>Home {props.children}</div>
|
||||
},
|
||||
Login: () => <div>Login</div>,
|
||||
},
|
||||
});
|
||||
|
||||
export default () => {
|
||||
const routes: Array<RouteRedirectProps> = [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/login',
|
||||
exact: true,
|
||||
component: 'Login',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/home',
|
||||
component: 'Home',
|
||||
routes: [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/home/123',
|
||||
exact: true,
|
||||
component: 'Login',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<Router initialEntries={['/home/123']}>
|
||||
<RouteSwitch routes={routes} />
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
};
|
65
packages/client/src/components/router-config/demos/demo2.tsx
Normal file
65
packages/client/src/components/router-config/demos/demo2.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
Link,
|
||||
useLocation,
|
||||
useRouteMatch,
|
||||
MemoryRouter as Router,
|
||||
} from 'react-router-dom';
|
||||
import { createRouteSwitch, RouteRedirectProps } from '..';
|
||||
import { AdminLayout } from '../../admin-layout';
|
||||
import { AuthLayout } from '../../auth-layout';
|
||||
import { RouteSchemaRenderer } from '../../route-schema-renderer';
|
||||
|
||||
const RouteSwitch = createRouteSwitch({
|
||||
components: {
|
||||
AdminLayout,
|
||||
AuthLayout,
|
||||
RouteSchemaRenderer,
|
||||
},
|
||||
});
|
||||
|
||||
const routes: Array<RouteRedirectProps> = [
|
||||
{
|
||||
type: 'redirect',
|
||||
from: '/',
|
||||
to: '/admin',
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'AdminLayout',
|
||||
title: `后台`,
|
||||
uiSchemaKey: 'qqzzjakwkwl',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
component: 'AuthLayout',
|
||||
children: [
|
||||
{
|
||||
type: 'route',
|
||||
path: '/login',
|
||||
component: 'RouteSchemaRenderer',
|
||||
title: `登录`,
|
||||
uiSchemaKey: 'dtf9j0b8p9u',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/register',
|
||||
component: 'RouteSchemaRenderer',
|
||||
title: `注册`,
|
||||
uiSchemaKey: '46qlxqam3xk',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<Router initialEntries={['/']}>
|
||||
<RouteSwitch routes={routes} />
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
};
|
3
packages/client/src/components/router-config/index.md
Normal file
3
packages/client/src/components/router-config/index.md
Normal file
@ -0,0 +1,3 @@
|
||||
<code src="./demos/demo1.tsx"/>
|
||||
|
||||
<code src="./demos/demo2.tsx"/>
|
105
packages/client/src/components/router-config/index.tsx
Normal file
105
packages/client/src/components/router-config/index.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import { get } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { createContext } from 'react';
|
||||
import { Switch, Route, Redirect } from 'react-router';
|
||||
|
||||
export interface RedirectProps {
|
||||
type: 'redirect';
|
||||
to: any;
|
||||
path?: string;
|
||||
exact?: boolean;
|
||||
strict?: boolean;
|
||||
push?: boolean;
|
||||
from?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface RouteProps {
|
||||
type: 'route';
|
||||
path?: string | string[];
|
||||
exact?: boolean;
|
||||
strict?: boolean;
|
||||
sensitive?: boolean;
|
||||
component?: any;
|
||||
routes?: RouteProps[],
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type RouteRedirectProps = RedirectProps | RouteProps;
|
||||
|
||||
export interface RouteSwitchOptions {
|
||||
routes?: RouteRedirectProps[];
|
||||
components?: any;
|
||||
}
|
||||
|
||||
export interface RouteSwitchProps {
|
||||
routes?: RouteRedirectProps[];
|
||||
components?: any;
|
||||
}
|
||||
|
||||
export const RouteComponentsContext = createContext(null);
|
||||
|
||||
export function useComponent(route: RouteProps) {
|
||||
const components = useContext(RouteComponentsContext);
|
||||
if (typeof route.component === 'string') {
|
||||
const component = get(components, route.component);
|
||||
return component;
|
||||
}
|
||||
return route.component || (() => null);
|
||||
}
|
||||
|
||||
export function createRouteSwitch(options: RouteSwitchOptions) {
|
||||
function ComponentRenderer(props) {
|
||||
const Component = useComponent(props.route);
|
||||
return (
|
||||
<Component {...props}>
|
||||
<RouteSwitch routes={props.route.routes} />
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteSwitch(props: RouteSwitchProps) {
|
||||
const { routes = [] } = props;
|
||||
if (!routes.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<RouteComponentsContext.Provider
|
||||
value={{ ...options.components, ...props.components }}
|
||||
>
|
||||
<Switch>
|
||||
{routes.map((route) => {
|
||||
if (route.type == 'redirect') {
|
||||
return (
|
||||
<Redirect
|
||||
to={route.to}
|
||||
push={route.push}
|
||||
from={route.from}
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
strict={route.strict}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!route.path && Array.isArray(route.routes)) {
|
||||
route.path = route.routes.map((r) => r.path) as any;
|
||||
}
|
||||
return (
|
||||
<Route
|
||||
path={route.path}
|
||||
exact={route.exact}
|
||||
strict={route.strict}
|
||||
sensitive={route.sensitive}
|
||||
render={(props) => {
|
||||
return <ComponentRenderer {...props} route={route} />;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Switch>
|
||||
</RouteComponentsContext.Provider>
|
||||
);
|
||||
}
|
||||
return RouteSwitch;
|
||||
}
|
@ -1,12 +1,4 @@
|
||||
import Mock from 'mockjs';
|
||||
|
||||
export default [
|
||||
{
|
||||
type: 'redirect',
|
||||
from: '/admin',
|
||||
to: '/admin/item2',
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
type: 'redirect',
|
||||
from: '/',
|
||||
@ -14,28 +6,30 @@ export default [
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'AdminLayout',
|
||||
title: `后台 - ${Mock.mock('@string')}`,
|
||||
schemaName: 'menu',
|
||||
title: `后台`,
|
||||
uiSchemaKey: 'qqzzjakwkwl',
|
||||
},
|
||||
{
|
||||
type: 'route',
|
||||
component: 'AuthLayout',
|
||||
routes: [
|
||||
children: [
|
||||
{
|
||||
name: 'login',
|
||||
type: 'route',
|
||||
path: '/login',
|
||||
component: 'DefaultPage',
|
||||
title: `登录 - ${Mock.mock('@string')}`,
|
||||
schemaName: 'login',
|
||||
component: 'RouteSchemaRenderer',
|
||||
title: `登录`,
|
||||
uiSchemaKey: 'dtf9j0b8p9u',
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
type: 'route',
|
||||
path: '/register',
|
||||
component: 'DefaultPage',
|
||||
title: `注册 - ${Mock.mock('@string')}`,
|
||||
schemaName: 'register',
|
||||
component: 'RouteSchemaRenderer',
|
||||
title: `注册`,
|
||||
uiSchemaKey: '46qlxqam3xk',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
];
|
||||
|
@ -1,43 +1,51 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { Spin } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
RouteSwitch,
|
||||
useGlobalAction,
|
||||
AdminLayout,
|
||||
AuthLayout,
|
||||
DefaultPage,
|
||||
} from '@nocobase/client';
|
||||
import {
|
||||
Link,
|
||||
useHistory,
|
||||
useLocation,
|
||||
useRouteMatch,
|
||||
MemoryRouter as Router,
|
||||
} from 'react-router-dom';
|
||||
import { UseRequestProvider } from 'ahooks';
|
||||
import { request } from './api';
|
||||
|
||||
const templates = {
|
||||
import {
|
||||
createRouteSwitch,
|
||||
RouteRedirectProps,
|
||||
AdminLayout,
|
||||
AuthLayout,
|
||||
DefaultPage,
|
||||
};
|
||||
RouteSchemaRenderer,
|
||||
} from '../';
|
||||
import { UseRequestProvider } from 'ahooks';
|
||||
import { extend } from 'umi-request';
|
||||
|
||||
const request = extend({
|
||||
prefix: 'http://localhost:23003/api/',
|
||||
timeout: 1000,
|
||||
});
|
||||
|
||||
const RouteSwitch = createRouteSwitch({
|
||||
components: {
|
||||
AdminLayout,
|
||||
AuthLayout,
|
||||
RouteSchemaRenderer,
|
||||
},
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { data, loading } = useRequest('routes:getAccessible', {
|
||||
formatResult: (result) => result?.data,
|
||||
});
|
||||
|
||||
function App() {
|
||||
const { data, loading } = useGlobalAction('routes:getAccessible');
|
||||
if (loading) {
|
||||
return <Spin />;
|
||||
return <Spin/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Router initialEntries={['/login']}>
|
||||
<RouteSwitch components={templates} routes={data} />
|
||||
<RouteSwitch routes={data} />
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default function IndexPage() {
|
||||
export default () => {
|
||||
return (
|
||||
<UseRequestProvider
|
||||
value={{
|
||||
|
@ -1,6 +1,5 @@
|
||||
export * from './state';
|
||||
// export * from './blocks';
|
||||
export * from './templates';
|
||||
// export * from './fields';
|
||||
export * from './components/RouteSwitch';
|
||||
export { Drawer } from './components/Drawer';
|
||||
export * from './components/router-config';
|
||||
export * from './components/admin-layout';
|
||||
export * from './components/auth-layout';
|
||||
export * from './components/route-schema-renderer';
|
||||
export * from './schemas';
|
||||
|
@ -207,6 +207,9 @@ export function useDesignable(path?: any) {
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
if (target['key']) {
|
||||
property['parentKey'] = target['key'];
|
||||
}
|
||||
const properties = {};
|
||||
properties[property.name] = property;
|
||||
Object.keys(target.properties).forEach((name, index) => {
|
||||
@ -231,6 +234,9 @@ export function useDesignable(path?: any) {
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
if (target['key']) {
|
||||
property['parentKey'] = target['key'];
|
||||
}
|
||||
target.addProperty(property.name, property);
|
||||
// BUG: 空 properties 时,addProperty 无反应。
|
||||
const tmp = { name: uid() };
|
||||
@ -251,6 +257,9 @@ export function useDesignable(path?: any) {
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
if (target['parentKey']) {
|
||||
property['parentKey'] = target['parentKey'];
|
||||
}
|
||||
addPropertyAfter(target, property);
|
||||
refresh();
|
||||
return target.parent.properties[property.name];
|
||||
@ -267,6 +276,9 @@ export function useDesignable(path?: any) {
|
||||
if (!property.name) {
|
||||
property.name = uid();
|
||||
}
|
||||
if (target['parentKey']) {
|
||||
property['parentKey'] = target['parentKey'];
|
||||
}
|
||||
addPropertyBefore(target, property);
|
||||
refresh();
|
||||
return target.parent.properties[property.name];
|
||||
|
@ -412,6 +412,68 @@ export default () => {
|
||||
}
|
||||
```
|
||||
|
||||
### 设计器模式 - 菜单项为空时
|
||||
|
||||
```tsx
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { SchemaRenderer } from '../';
|
||||
import { MenuContainerContext } from './';
|
||||
import { Layout } from 'antd';
|
||||
|
||||
export default () => {
|
||||
const sideMenuRef = useRef();
|
||||
|
||||
const [activeKey, setActiveKey] = useState('item3');
|
||||
|
||||
const onSelect = (info) => {
|
||||
setActiveKey(info.key);
|
||||
console.log({ info })
|
||||
}
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
menu1: {
|
||||
type: 'void',
|
||||
'x-component': 'Menu',
|
||||
'x-designable-bar': 'Menu.DesignableBar',
|
||||
'x-component-props': {
|
||||
defaultSelectedKeys: [activeKey],
|
||||
mode: 'mix',
|
||||
theme: 'dark',
|
||||
sideMenuRef: '{{ sideMenuRef }}',
|
||||
onSelect: '{{ onSelect }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Layout>
|
||||
<Layout.Header>
|
||||
<SchemaRenderer
|
||||
schema={schema}
|
||||
scope={{ onSelect, sideMenuRef }}
|
||||
/>
|
||||
</Layout.Header>
|
||||
<Layout>
|
||||
<Layout.Sider
|
||||
ref={sideMenuRef}
|
||||
theme={'light'}
|
||||
width={200}
|
||||
>
|
||||
</Layout.Sider>
|
||||
<Layout.Content>
|
||||
{activeKey}
|
||||
</Layout.Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Menu.Action
|
||||
|
||||
|
||||
|
@ -94,22 +94,30 @@ function useDesignableBar() {
|
||||
}
|
||||
|
||||
export const Menu: MenuType = observer((props: any) => {
|
||||
const { sideMenuRef, onSelect, mode, defaultSelectedKeys, ...others } = props;
|
||||
const {
|
||||
children,
|
||||
sideMenuRef,
|
||||
onSelect,
|
||||
mode,
|
||||
defaultSelectedKeys,
|
||||
...others
|
||||
} = props;
|
||||
let defaultSelectedKey = defaultSelectedKeys ? defaultSelectedKeys[0] : null;
|
||||
const schema = useFieldSchema();
|
||||
const { schema: designableSchema, refresh } = useDesignable();
|
||||
// const schema = useFieldSchema();
|
||||
const { schema, schema: designableSchema, refresh } = useDesignable();
|
||||
const designableBar = schema['x-designable-bar'];
|
||||
const history = useHistory();
|
||||
const renderSideMenu = (selectedKey) => {
|
||||
if (!selectedKey) {
|
||||
return;
|
||||
}
|
||||
if ((mode as any) !== 'mix') {
|
||||
return;
|
||||
}
|
||||
if (!sideMenuRef || !sideMenuRef.current) {
|
||||
return;
|
||||
}
|
||||
if (!selectedKey || !schema.properties) {
|
||||
sideMenuRef.current.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const subSchema = schema.properties[selectedKey];
|
||||
if (!subSchema) {
|
||||
sideMenuRef.current.style.display = 'none';
|
||||
@ -160,6 +168,7 @@ export const Menu: MenuType = observer((props: any) => {
|
||||
...newProps,
|
||||
[`${uid()}-add-new`]: {
|
||||
type: 'void',
|
||||
parentKey: subSchema['key'],
|
||||
'x-component': 'Menu.AddNew',
|
||||
},
|
||||
},
|
||||
@ -173,6 +182,8 @@ export const Menu: MenuType = observer((props: any) => {
|
||||
console.log({ defaultSelectedKey }, schema.properties);
|
||||
renderSideMenu(defaultSelectedKey);
|
||||
});
|
||||
const isEmpty = !Object.keys(designableSchema.properties || {}).length;
|
||||
console.log({ designableSchema })
|
||||
return (
|
||||
<MenuContext.Provider
|
||||
value={{
|
||||
@ -189,13 +200,44 @@ export const Menu: MenuType = observer((props: any) => {
|
||||
onSelect && onSelect(info);
|
||||
}}
|
||||
mode={(mode as any) === 'mix' ? 'horizontal' : mode}
|
||||
/>
|
||||
>
|
||||
{!isEmpty ? (
|
||||
<RecursionField schema={schema} onlyRenderProperties />
|
||||
) : (
|
||||
<SchemaRenderer
|
||||
onRefresh={(subSchema: Schema) => {
|
||||
Object.keys(subSchema.properties).forEach((name) => {
|
||||
if (name === 'add-new') {
|
||||
return;
|
||||
}
|
||||
designableSchema.addProperty(
|
||||
name,
|
||||
subSchema.properties[name].toJSON(),
|
||||
);
|
||||
});
|
||||
refresh();
|
||||
}}
|
||||
schema={{
|
||||
type: 'object',
|
||||
properties: {
|
||||
'add-new': {
|
||||
parentKey: schema['key'],
|
||||
type: 'void',
|
||||
'x-component': 'Menu.AddNew',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AntdMenu>
|
||||
</MenuContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
const AddNewAction = () => {
|
||||
const { insertBefore } = useDesignable();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { schema, insertBefore } = useDesignable();
|
||||
console.log('AddNewAction', schema, fieldSchema);
|
||||
return (
|
||||
<Dropdown
|
||||
overlayStyle={{
|
||||
@ -206,11 +248,13 @@ const AddNewAction = () => {
|
||||
<AntdMenu>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
insertBefore({
|
||||
const s = insertBefore({
|
||||
type: 'void',
|
||||
title: uid(),
|
||||
key: uid(),
|
||||
'x-component': 'Menu.Item',
|
||||
});
|
||||
console.log('s.s.s.s.s.s', s)
|
||||
}}
|
||||
style={{ minWidth: 150 }}
|
||||
>
|
||||
@ -375,7 +419,7 @@ Menu.DesignableBar = (props) => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { schema, remove, refresh } = useDesignable();
|
||||
const { schema, remove, refresh, insertAfter } = useDesignable();
|
||||
return (
|
||||
<div className={cls('designable-bar', { active: visible })}>
|
||||
<div
|
||||
@ -414,6 +458,14 @@ Menu.DesignableBar = (props) => {
|
||||
>
|
||||
修改标题
|
||||
</AntdMenu.Item>
|
||||
<AntdMenu.Item onClick={() => {
|
||||
const s = insertAfter({
|
||||
type: 'void',
|
||||
key: uid(),
|
||||
title: uid(),
|
||||
'x-component': 'Menu.SubMenu',
|
||||
});
|
||||
}}>插入</AntdMenu.Item>
|
||||
<AntdMenu.Item
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
|
@ -1,6 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { Model } from '@nocobase/database';
|
||||
|
||||
export class Route extends Model {
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nocobase/plugin-routes",
|
||||
"name": "@nocobase/plugin-ui-router",
|
||||
"version": "0.4.0-alpha.7",
|
||||
"main": "lib/index.js",
|
||||
"license": "MIT",
|
||||
@ -7,7 +7,8 @@
|
||||
"@nocobase/database": "^0.4.0-alpha.7",
|
||||
"@nocobase/resourcer": "^0.4.0-alpha.7",
|
||||
"@nocobase/server": "^0.4.0-alpha.7",
|
||||
"deepmerge": "^4.2.2"
|
||||
"deepmerge": "^4.2.2",
|
||||
"flat-to-nested": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/actions": "^0.4.0-alpha.7"
|
@ -15,20 +15,20 @@ describe('routes', () => {
|
||||
|
||||
afterEach(() => app.database.close());
|
||||
|
||||
it.only('create route', async () => {
|
||||
const Route = db.getModel('routes');
|
||||
const item = {
|
||||
path: '/admin/:name(.+)?',
|
||||
component: 'AdminLayout',
|
||||
title: `后台`,
|
||||
uiSchema: {
|
||||
name: 'menu',
|
||||
},
|
||||
};
|
||||
console.log(Route.associations);
|
||||
const route = await Route.create(item);
|
||||
await route.updateAssociations(item);
|
||||
});
|
||||
// it.only('create route', async () => {
|
||||
// const Route = db.getModel('routes');
|
||||
// const item = {
|
||||
// path: '/admin/:name(.+)?',
|
||||
// component: 'AdminLayout',
|
||||
// title: `后台`,
|
||||
// uiSchema: {
|
||||
// name: 'menu',
|
||||
// },
|
||||
// };
|
||||
// console.log(Route.associations);
|
||||
// const route = await Route.create(item);
|
||||
// await route.updateAssociations(item);
|
||||
// });
|
||||
|
||||
it('create route', async () => {
|
||||
const Route = db.getModel('routes');
|
18
packages/plugin-ui-router/src/actions/getAccessible.ts
Normal file
18
packages/plugin-ui-router/src/actions/getAccessible.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Model, ModelCtor } from '@nocobase/database';
|
||||
import { actions } from '@nocobase/actions';
|
||||
import FlatToNested from 'flat-to-nested';
|
||||
|
||||
const flatToNested = new FlatToNested({
|
||||
id: 'key',
|
||||
parent: 'parentKey',
|
||||
children: 'routes',
|
||||
});
|
||||
|
||||
export default async (ctx: actions.Context, next: actions.Next) => {
|
||||
const { resourceKey } = ctx.action.params;
|
||||
const Route = ctx.db.getModel('routes');
|
||||
const routes = await Route.findAll();
|
||||
const data = flatToNested.convert(routes.map(route => route.toProps()));
|
||||
ctx.body = data.routes;
|
||||
await next();
|
||||
}
|
@ -3,6 +3,7 @@ import { TableOptions } from '@nocobase/database';
|
||||
export default {
|
||||
name: 'routes',
|
||||
title: '路由表',
|
||||
model: 'Route',
|
||||
fields: [
|
||||
{
|
||||
type: 'uid',
|
33
packages/plugin-ui-router/src/models/route.ts
Normal file
33
packages/plugin-ui-router/src/models/route.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import _ from 'lodash';
|
||||
import { Model } from '@nocobase/database';
|
||||
|
||||
export class Route extends Model {
|
||||
static async create(value?: any, options?: any): Promise<any> {
|
||||
// console.log({ value });
|
||||
const attributes = this.toAttributes(value);
|
||||
// @ts-ignore
|
||||
const model: Model = await super.create(attributes, options);
|
||||
return model;
|
||||
}
|
||||
|
||||
static toAttributes(value = {}): any {
|
||||
const data = _.cloneDeep(value);
|
||||
const keys = [
|
||||
...Object.keys(this.rawAttributes),
|
||||
...Object.keys(this.associations),
|
||||
];
|
||||
const attrs = _.pick(data, keys);
|
||||
const options = _.omit(data, keys);
|
||||
return { ...attrs, options };
|
||||
}
|
||||
|
||||
toProps() {
|
||||
const json = this.toJSON();
|
||||
const data: any = _.omit(json, ['options', 'created_at', 'updated_at', 'ui_schema_key']);
|
||||
const options = json['options'] || {};
|
||||
if (json['ui_schema_key']) {
|
||||
data.uiSchemaKey = json['ui_schema_key'];
|
||||
}
|
||||
return { ...data, ...options }
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import path from 'path';
|
||||
import { Application } from '@nocobase/server';
|
||||
import { registerModels } from '@nocobase/database';
|
||||
import * as models from './models';
|
||||
import getAccessible from './actions/getAccessible';
|
||||
|
||||
export default async function (this: Application, options = {}) {
|
||||
const database = this.database;
|
||||
@ -10,4 +11,6 @@ export default async function (this: Application, options = {}) {
|
||||
database.import({
|
||||
directory: path.resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
this.resourcer.registerActionHandler('routes:getAccessible', getAccessible);
|
||||
}
|
15
packages/plugin-ui-schema/src/actions/getTree.ts
Normal file
15
packages/plugin-ui-schema/src/actions/getTree.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Model, ModelCtor } from '@nocobase/database';
|
||||
import { actions } from '@nocobase/actions';
|
||||
|
||||
export default async (ctx: actions.Context, next: actions.Next) => {
|
||||
const { resourceKey } = ctx.action.params;
|
||||
const UISchema = ctx.db.getModel('ui_schemas');
|
||||
const schema = await UISchema.findByPk(resourceKey);
|
||||
const property = schema.toProperty();
|
||||
const properties = await schema.getProperties();
|
||||
if (Object.keys(properties).length) {
|
||||
property.properties = properties;
|
||||
}
|
||||
ctx.body = property;
|
||||
await next();
|
||||
}
|
@ -2,6 +2,7 @@ import path from 'path';
|
||||
import { Application } from '@nocobase/server';
|
||||
import { registerModels, Table } from '@nocobase/database';
|
||||
import * as models from './models';
|
||||
import getTree from './actions/getTree';
|
||||
|
||||
export default async function (this: Application, options = {}) {
|
||||
const database = this.database;
|
||||
@ -11,9 +12,11 @@ export default async function (this: Application, options = {}) {
|
||||
directory: path.resolve(__dirname, 'collections'),
|
||||
});
|
||||
|
||||
database.getModel('ui_schemas').beforeCreate((model) => {
|
||||
if (!model.get('name')) {
|
||||
model.set('name', model.get('key'));
|
||||
}
|
||||
});
|
||||
// database.getModel('ui_schemas').beforeCreate((model) => {
|
||||
// if (!model.get('name')) {
|
||||
// model.set('name', model.get('key'));
|
||||
// }
|
||||
// });
|
||||
|
||||
this.resourcer.registerActionHandler('ui_schemas:getTree', getTree);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import compose from 'koa-compose';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
import Resourcer, { getNameByParams, KoaMiddlewareOptions, parseRequest, parseQuery, ResourcerContext, ResourceType } from '@nocobase/resourcer';
|
||||
import Database, { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import compose from 'koa-compose';
|
||||
import { pathToRegexp } from 'path-to-regexp';
|
||||
import pathToRegexp from 'path-to-regexp';
|
||||
import Resourcer, { getNameByParams, KoaMiddlewareOptions, parseRequest, parseQuery, ResourcerContext, ResourceType } from '@nocobase/resourcer';
|
||||
import Database, { BELONGSTO, BELONGSTOMANY, HASMANY, HASONE } from '@nocobase/database';
|
||||
|
||||
|
191
yarn.lock
191
yarn.lock
@ -5278,6 +5278,20 @@ boxen@^3.0.0:
|
||||
type-fest "^0.3.0"
|
||||
widest-line "^2.0.0"
|
||||
|
||||
boxen@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
|
||||
integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==
|
||||
dependencies:
|
||||
ansi-align "^3.0.0"
|
||||
camelcase "^5.3.1"
|
||||
chalk "^3.0.0"
|
||||
cli-boxes "^2.2.0"
|
||||
string-width "^4.1.0"
|
||||
term-size "^2.1.0"
|
||||
type-fest "^0.8.1"
|
||||
widest-line "^3.1.0"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
@ -5702,6 +5716,14 @@ chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad"
|
||||
@ -5803,7 +5825,7 @@ chokidar@^2.0.4:
|
||||
optionalDependencies:
|
||||
fsevents "^1.2.7"
|
||||
|
||||
chokidar@^3.0.2:
|
||||
chokidar@^3.0.2, chokidar@^3.2.2:
|
||||
version "3.5.2"
|
||||
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
|
||||
integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
|
||||
@ -6187,6 +6209,18 @@ configstore@^4.0.0:
|
||||
write-file-atomic "^2.0.0"
|
||||
xdg-basedir "^3.0.0"
|
||||
|
||||
configstore@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
|
||||
integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==
|
||||
dependencies:
|
||||
dot-prop "^5.2.0"
|
||||
graceful-fs "^4.1.2"
|
||||
make-dir "^3.0.0"
|
||||
unique-string "^2.0.0"
|
||||
write-file-atomic "^3.0.0"
|
||||
xdg-basedir "^4.0.0"
|
||||
|
||||
connected-react-router@6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.npmjs.org/connected-react-router/-/connected-react-router-6.5.2.tgz#422af70f86cb276681e20ab4295cf27dd9b6c7e3"
|
||||
@ -6534,6 +6568,11 @@ crypto-random-string@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
||||
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
|
||||
|
||||
crypto-random-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
|
||||
|
||||
crypto-random-string@^3.3.0, crypto-random-string@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-3.3.1.tgz#13cee94cac8001e4842501608ef779e0ed08f82d"
|
||||
@ -6878,7 +6917,7 @@ debug@3.1.0, debug@~3.1.0:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@3.X, debug@^3.1.0, debug@^3.2.7:
|
||||
debug@3.X, debug@^3.1.0, debug@^3.2.6, debug@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
||||
@ -7679,6 +7718,11 @@ escalade@^3.1.1:
|
||||
resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
escape-goat@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675"
|
||||
integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==
|
||||
|
||||
escape-html@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
@ -8370,6 +8414,11 @@ flat-cache@^3.0.4:
|
||||
flatted "^3.1.0"
|
||||
rimraf "^3.0.2"
|
||||
|
||||
flat-to-nested@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/flat-to-nested/-/flat-to-nested-1.1.1.tgz#ec183cd9a72f6bfbf8ca21acb0fc8235e509f581"
|
||||
integrity sha512-Sym5oik6BO9JnsDEjv9Q9hPTCexG2ttk0UiM2mgLEiCiiUOQr8acBd33r8ixnoSGR0HAxPoP8WtLAL5oV46IhQ==
|
||||
|
||||
flatted@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
|
||||
@ -8811,6 +8860,13 @@ global-dirs@^0.1.0:
|
||||
dependencies:
|
||||
ini "^1.3.4"
|
||||
|
||||
global-dirs@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d"
|
||||
integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==
|
||||
dependencies:
|
||||
ini "1.3.7"
|
||||
|
||||
global@^4.3.2:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.npmjs.org/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
|
||||
@ -9480,6 +9536,11 @@ iferr@^0.1.5:
|
||||
resolved "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
|
||||
integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
|
||||
|
||||
ignore-by-default@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||
integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk=
|
||||
|
||||
ignore-walk@^3.0.1:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335"
|
||||
@ -9647,6 +9708,11 @@ inherits@2.0.3:
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
ini@1.3.7:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
|
||||
integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
|
||||
|
||||
ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
@ -9998,6 +10064,14 @@ is-installed-globally@^0.1.0:
|
||||
global-dirs "^0.1.0"
|
||||
is-path-inside "^1.0.0"
|
||||
|
||||
is-installed-globally@^0.3.1:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141"
|
||||
integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==
|
||||
dependencies:
|
||||
global-dirs "^2.0.1"
|
||||
is-path-inside "^3.0.1"
|
||||
|
||||
is-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
|
||||
@ -10026,6 +10100,11 @@ is-npm@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/is-npm/-/is-npm-3.0.0.tgz#ec9147bfb629c43f494cf67936a961edec7e8053"
|
||||
integrity sha512-wsigDr1Kkschp2opC4G3yA6r9EgVA6NjRpWzIi9axXqeIaAATPRJc4uLujXe3Nd9uO8KoDyA4MD6aZSeXTADhA==
|
||||
|
||||
is-npm@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d"
|
||||
integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==
|
||||
|
||||
is-number-object@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
|
||||
@ -10060,6 +10139,11 @@ is-path-inside@^1.0.0:
|
||||
dependencies:
|
||||
path-is-inside "^1.0.1"
|
||||
|
||||
is-path-inside@^3.0.1:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
|
||||
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
|
||||
|
||||
is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
|
||||
@ -12889,6 +12973,22 @@ node-schedule@^2.0.0:
|
||||
long-timeout "0.1.1"
|
||||
sorted-array-functions "^1.3.0"
|
||||
|
||||
nodemon@^2.0.12:
|
||||
version "2.0.12"
|
||||
resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.12.tgz#5dae4e162b617b91f1873b3bfea215dd71e144d5"
|
||||
integrity sha512-egCTmNZdObdBxUBw6ZNwvZ/xzk24CKRs5K6d+5zbmrMr7rOpPmfPeF6OxM3DDpaRx331CQRFEktn+wrFFfBSOA==
|
||||
dependencies:
|
||||
chokidar "^3.2.2"
|
||||
debug "^3.2.6"
|
||||
ignore-by-default "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
pstree.remy "^1.1.7"
|
||||
semver "^5.7.1"
|
||||
supports-color "^5.5.0"
|
||||
touch "^3.1.0"
|
||||
undefsafe "^2.0.3"
|
||||
update-notifier "^4.1.0"
|
||||
|
||||
nopt@^4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
|
||||
@ -12904,6 +13004,13 @@ nopt@^5.0.0:
|
||||
dependencies:
|
||||
abbrev "1"
|
||||
|
||||
nopt@~1.0.10:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
|
||||
integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
|
||||
dependencies:
|
||||
abbrev "1"
|
||||
|
||||
normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||
@ -14812,6 +14919,11 @@ psl@^1.1.28, psl@^1.1.33:
|
||||
resolved "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
|
||||
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
|
||||
|
||||
pstree.remy@^1.1.7:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
|
||||
integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
|
||||
|
||||
public-encrypt@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
|
||||
@ -14864,6 +14976,13 @@ punycode@^2.1.0, punycode@^2.1.1:
|
||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
pupa@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62"
|
||||
integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==
|
||||
dependencies:
|
||||
escape-goat "^2.0.0"
|
||||
|
||||
q@^1.1.2, q@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
@ -16771,6 +16890,13 @@ semver-diff@^2.0.0:
|
||||
dependencies:
|
||||
semver "^5.0.3"
|
||||
|
||||
semver-diff@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
|
||||
integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==
|
||||
dependencies:
|
||||
semver "^6.3.0"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
@ -17385,7 +17511,7 @@ string-width@^3.0.0, string-width@^3.1.0:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^5.1.0"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0:
|
||||
string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
|
||||
integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
|
||||
@ -17626,7 +17752,7 @@ supports-color@^3.2.3:
|
||||
dependencies:
|
||||
has-flag "^1.0.0"
|
||||
|
||||
supports-color@^5.3.0, supports-color@^5.4.0:
|
||||
supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
@ -17755,6 +17881,11 @@ term-size@^1.2.0:
|
||||
dependencies:
|
||||
execa "^0.7.0"
|
||||
|
||||
term-size@^2.1.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54"
|
||||
integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
|
||||
|
||||
terminal-link@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
|
||||
@ -18024,6 +18155,13 @@ toposort-class@^1.0.1:
|
||||
resolved "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988"
|
||||
integrity sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=
|
||||
|
||||
touch@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
|
||||
integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
|
||||
dependencies:
|
||||
nopt "~1.0.10"
|
||||
|
||||
tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0, tough-cookie@~2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
||||
@ -18408,6 +18546,13 @@ unc-path-regex@^0.1.2:
|
||||
resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
|
||||
|
||||
undefsafe@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae"
|
||||
integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==
|
||||
dependencies:
|
||||
debug "^2.2.0"
|
||||
|
||||
underscore@^1.13.1:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz#0c1c6bd2df54b6b69f2314066d65b6cde6fcf9d1"
|
||||
@ -18503,6 +18648,13 @@ unique-string@^1.0.0:
|
||||
dependencies:
|
||||
crypto-random-string "^1.0.0"
|
||||
|
||||
unique-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d"
|
||||
integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==
|
||||
dependencies:
|
||||
crypto-random-string "^2.0.0"
|
||||
|
||||
unist-builder@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436"
|
||||
@ -18619,6 +18771,25 @@ update-notifier@3.0.0:
|
||||
semver-diff "^2.0.0"
|
||||
xdg-basedir "^3.0.0"
|
||||
|
||||
update-notifier@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3"
|
||||
integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==
|
||||
dependencies:
|
||||
boxen "^4.2.0"
|
||||
chalk "^3.0.0"
|
||||
configstore "^5.0.1"
|
||||
has-yarn "^2.1.0"
|
||||
import-lazy "^2.1.0"
|
||||
is-ci "^2.0.0"
|
||||
is-installed-globally "^0.3.1"
|
||||
is-npm "^4.0.0"
|
||||
is-yarn-global "^0.3.0"
|
||||
latest-version "^5.0.0"
|
||||
pupa "^2.0.1"
|
||||
semver-diff "^3.1.1"
|
||||
xdg-basedir "^4.0.0"
|
||||
|
||||
upper-case@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a"
|
||||
@ -19084,6 +19255,13 @@ widest-line@^2.0.0:
|
||||
dependencies:
|
||||
string-width "^2.1.1"
|
||||
|
||||
widest-line@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
|
||||
integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
|
||||
dependencies:
|
||||
string-width "^4.0.0"
|
||||
|
||||
wildcard@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz#a7020453084d8cd2efe70ba9d3696263de1710a5"
|
||||
@ -19237,6 +19415,11 @@ xdg-basedir@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
||||
integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
|
||||
|
||||
xdg-basedir@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||
integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==
|
||||
|
||||
xml-name-validator@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||
|
Loading…
Reference in New Issue
Block a user