Skip to content

Commit

Permalink
docs: finish docs
Browse files Browse the repository at this point in the history
  • Loading branch information
leftstick committed Mar 21, 2020
1 parent ac65e91 commit 327c095
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 84 deletions.
Binary file added docs/.vuepress/public/locale.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
home: true
heroText: umi 中台下总成
heroText: umi 中台助手
tagline: 快速构建一个中台系统所需的基础元素
actionText: 新手上路 →
actionLink: /guide/
Expand Down Expand Up @@ -45,7 +45,7 @@ footer: MIT Licensed | Copyright © 2020-present Howard.Zuo

## 快速开始

安装 umi 下总成
安装 umi 中台助手

```bash
yarn global add yo generator-umi
Expand Down
23 changes: 23 additions & 0 deletions docs/guide/data.md
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
# 数据

数据管理使用详情,参考[plugin-model](https://umijs.org/zh-CN/plugins/plugin-model)

核心思路就是利用 `hooks` 来做 model,是有别于 `redux` 的另一种数据管理手段。

我们依旧以登录页面为例讲解。

`src/pages/o/login/index.tsx`

```typescript
// 业务逻辑被提取到了 src/models/useLoginModel.ts 中
// 本组件只用到了 initBackground
const { initBackground } = useModel('useLoginModel', m => pick(m, 'initBackground'))

// 然后组件挂载后,执行该方法,我们就得到了一个酷炫的背景
useEffect(() => {
initBackground()
}, [initBackground])
```

至于这种新的风格,基于 `hooks` 的 model 如何编写? 那真的简单的一批,就是纯纯的 自定义 `hooks`

打开 `src/models/useLoginModel.ts` 看看就知道了
59 changes: 38 additions & 21 deletions docs/guide/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,48 @@
脚手架扩展了 `umi` 的布局内容,分两个方面:

1. 提供了一些默认的布局(`BLANK``PRO_LAYOUT`),可供选择
2. 提供了布局选择器,在无需调整 `src/layouts/index.tsx` 的前提下,通过在 `src/layouts/options` 下按规则编写新的布局,然后在 `src/layouts/options/index.tsx` 将其注册到对应的路由形态的选择器列表里,就能在页面里使用了
2. 提供了布局选择器,在无需调整布局入口 `src/layouts/index.tsx` 的前提下,通过在 `src/layouts/options` 下按规则编写新的布局,然后在 `src/layouts/options/index.tsx` 将其注册到对应的路由形态的选择器列表里,就能在页面里使用了

> 关于 路由形态,不清楚的朋友 [来来来](/guide/route.md)
## 内置布局介绍

### BLANK

即:空布局,也是登录页面 `src/pages/o/login/index.tsx` 使用的布局。可以看到 登录页面里有指定其使用的布局为 `BLANK`,如下:

```typescript
Login.layout = 'BLANK'
```

### PRO_LAYOUT

即:ant-design pro 使用的由导航、侧边栏、内容区域 组成的布局。由于该布局导航里的用户信息,侧边栏的菜单都依赖用户数据,所以该布局只能被 AuthRequiredPage 使用。也是分析(`src/pages/dashboard/analysis/index.tsx`)、监控页面(`src/pages/dashboard/monitor/index.tsx`) 使用的布局。可以看到 这两个页面里有指定其使用的布局为 `PRO_LAYOUT`,如下:

```typescript
Monitor.layout = 'PRO_LAYOUT'
```

## 布局处理器列表

请看 `src/layouts/options/index.tsx`,找到如下代码:

```typescript
// 路由形态为 OpenPage 时的布局处理器列表
export const OPEN_LAYOUTS = [BlankResolver]
const OPEN_LAYOUTS = [BlankResolver]
// 路由形态为 AuthRequiredPage 时的布局处理器列表
export const AUTH_REQUIRED_LAYOUTS = [ProLayoutResolver, BlankResolver]
const AUTH_REQUIRED_LAYOUTS = [ProLayoutResolver, BlankResolver]
```

路由选择器执行逻辑为,先在布局入口 `src/layouts/index.tsx` 进行路由形态判定,然后根据形态在对应的布局处理器列表进行依次判定(从左至右),第一个匹配到的布局处理器,会被拿来对路由进行最终渲染。
路由处理器执行逻辑为,先在布局入口 `src/layouts/index.tsx` 进行路由形态判定,然后根据形态在对应的布局处理器列表进行依次判定(从左至右),第一个匹配到的布局处理器,会被拿来对路由进行最终渲染。

现在大家应该可以猜出,登录页面 (`/o/login`) 只会在 `OPEN_LAYOUTS` 里找合适的布局,而目前,`OPEN_LAYOUTS` 里,只有一个内置布局 `BLANK`,未来,开发者可以增加自己新的针对 OpenPage 的布局,只要添加到 `OPEN_LAYOUTS` 即可。

而 分析、监控 页面则只能被 `AUTH_REQUIRED_LAYOUTS` 注册使用,因为他们都需要用户登录,以及用户的特定权限。

## 路由指定布局

上面讲了布局处理器,那么我们开发一个页面,是如何指定这个页面使用哪个布局的呢? 记忆好的朋友已改已经想到 [路由](/guide/route.md#路由权限) ,提到的 `layout` 属性。没错,让我们再次打开 `src/pages/dashboard/analysis/index.tsx`
上面讲了布局处理器,那么我们开发一个页面,是如何指定这个页面使用哪个布局的呢? 有朋友应该已经注意到 上面提到的 `layout` 属性。没错,让我们再次打开 `src/pages/dashboard/analysis/index.tsx`

```typescript
import React from 'react'
Expand All @@ -45,7 +65,7 @@ Analysis.access = 'canReadDashboardAnalysis'
export default Analysis
```

## 自定义布局
## 自定义布局处理器

我们以 `BLANK` 布局为例,打开 `src/layouts/options/Blank/index.tsx`

Expand All @@ -58,10 +78,10 @@ import { isEmpty, pick } from '@/helpers/object'
import Exception403 from '@/components/exception/403'
import Exception404 from '@/components/exception/404'

import { ILayoutProps } from '@/types'
import { ILayoutProps, ILayoutResolver, IERoute } from '@/types'

// props 必须是 ILayoutProps 或 其子集
export default function Blank({ children, route, canAccess }: ILayoutProps) {
// 布局组件 props 必须是 ILayoutProps
function Blank({ children, route, canAccess }: ILayoutProps) {
const { width, height } = useModel('useAppModel', m => pick(m, 'width', 'height'))

// 如果路由信息不存在,当然,显示404
Expand All @@ -74,24 +94,17 @@ export default function Blank({ children, route, canAccess }: ILayoutProps) {
return <Exception403 style={{ width, height }} />
}

// children 就是路由对应的页面组件
return children
}
```
## 自定义布局处理器
我们依旧以 `BLANK` 的布局处理器为例(谁让它最简单呢),打开 `src/layouts/options/index.tsx`:

```typescript
// 必须是 ILayoutResolver
// 布局处理器
const BlankResolver: ILayoutResolver = {
// 用来判定布局入口传入的路由信息是否可以使用本布局渲染
// 判定传入路由是否可以被当前布局渲染
is(route?: IERoute): boolean {
return isEmpty(route) || route!.layout === 'BLANK' || route?.path === '/'
return isEmpty(route) || route!.layout === 'BLANK' || route!.path === '/'
},
// 用布局入口传入的 props 进行布局渲染。可以看到
// 这里 routes 其实 Blank 布局并没有用到,所以没有传入,可以避免不要的刷新
// 使用布局入口传入 props 渲染本布局
// 不是所有参数都要传给布局,只传必须项可以减少不必要的 re-render
get({ routes, children, route, canAccess }: ILayoutProps) {
return (
<Blank route={route} canAccess={canAccess}>
Expand All @@ -100,4 +113,8 @@ const BlankResolver: ILayoutResolver = {
)
}
}

export default BlankResolver
```
之后,只要把写好的布局处理器注册到 [布局处理器列表](/guide/layout.md#布局处理器列表) 里就可以使用了。
21 changes: 21 additions & 0 deletions docs/guide/locale.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
# 国际化

国际化使用详情,参考[plugin-locale](https://umijs.org/zh-CN/plugins/plugin-locale)

唯一要注意的是页面的 title。我们依旧以登录页面(`/src/pages/o/login/index.tsx`)为例:

```typescript
function Login() {
...
...
}

// title 在应用运行时,会被 src/locales/ 下对应语言文件里的内容替换
Login.title = 'LOGIN_TITLE'
Login.layout = 'BLANK'

export default Login
```

请看:

<img :src="$withBase('/locale.gif')" alt="locale">
6 changes: 6 additions & 0 deletions docs/guide/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
## 打包到 .zip

在根目录直接运行 `bash ./shells/build.sh` 可以生成 `.zip` 包,讲其放置目标服务器上解压,进入解压后的目录,运行 `yarn serve` 即可启动应用。

## 打包到 docker 镜像

在根目录直接运行 `docker build --no-cache -t <镜像名>:<版本号> .` 可以生成 镜像。

使用 `docker container run -e "API_HOST=<后端API_HOST>" -e "STORAGE_DOMAIN=<token存放在cookie中的domain>" -p <宿主端口>:3000 <镜像名>:<版本号>`
19 changes: 17 additions & 2 deletions generators/app/templates/src/layouts/options/Blank/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { isEmpty, pick } from '@/helpers/object'
import Exception403 from '@/components/exception/403'
import Exception404 from '@/components/exception/404'

import { ILayoutProps } from '@/types'
import { ILayoutProps, ILayoutResolver, IERoute } from '@/types'

export default function Blank({ children, route, canAccess }: ILayoutProps) {
function Blank({ children, route, canAccess }: ILayoutProps) {
const { width, height } = useModel('useAppModel', m => pick(m, 'width', 'height'))

if (isEmpty(route)) {
Expand All @@ -21,3 +21,18 @@ export default function Blank({ children, route, canAccess }: ILayoutProps) {

return children
}

const BlankResolver: ILayoutResolver = {
is(route?: IERoute): boolean {
return isEmpty(route) || route!.layout === 'BLANK' || route?.path === '/'
},
get({ routes, children, route, canAccess }: ILayoutProps) {
return (
<Blank route={route} canAccess={canAccess}>
{children}
</Blank>
)
}
}

export default BlankResolver
21 changes: 18 additions & 3 deletions generators/app/templates/src/layouts/options/ProLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import { Layout } from 'antd'
import Exception403 from '@/components/exception/403'
import Exception404 from '@/components/exception/404'

import NavigationBar from './NavigationBar'
import SideBarTitle from './SideBarTitle'
import SideBarMenu from './SideBarMenu'

import { isEmpty, pick } from '@/helpers/object'

import { ILayoutProps } from '@/types'
import { ILayoutProps, ILayoutResolver, IERoute } from '@/types'

import styles from './index.less'
import NavigationBar from './NavigationBar'

export default function ProLayout({ children, route, routes, canAccess }: ILayoutProps) {
function ProLayout({ children, route, routes, canAccess }: ILayoutProps) {
const { height } = useModel('useAppModel', m => pick(m, 'height'))

const { sidebarCollapsed, toggleSidebar } = useModel('useProLayoutModel', m =>
Expand Down Expand Up @@ -75,3 +75,18 @@ export default function ProLayout({ children, route, routes, canAccess }: ILayou
</Layout>
)
}

const ProLayoutResolver: ILayoutResolver = {
is(route?: IERoute): boolean {
return isEmpty(route) || route!.layout === 'PRO_LAYOUT'
},
get({ routes, children, route, canAccess }: ILayoutProps) {
return (
<ProLayout routes={routes!} route={route} canAccess={canAccess}>
{children}
</ProLayout>
)
}
}

export default ProLayoutResolver
53 changes: 12 additions & 41 deletions generators/app/templates/src/layouts/options/index.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,23 @@
import React from 'react'
import BlankResolver from './Blank'
import ProLayoutResolver from './ProLayout'
import { ILayoutProps, ILayoutResolver } from '@/types'
import { isNotEmpty } from '@/helpers/object'

import Blank from './Blank'
import ProLayout from './ProLayout'
import { isEmpty, isNotEmpty } from '@/helpers/object'

import { IERoute, ILayoutResolver, ILayoutProps } from '@/types'

const BlankResolver: ILayoutResolver = {
is(route?: IERoute): boolean {
return isEmpty(route) || route!.layout === 'BLANK' || route?.path === '/'
},
get({ routes, children, route, canAccess }: ILayoutProps) {
return (
<Blank route={route} canAccess={canAccess}>
{children}
</Blank>
)
}
}

const ProLayoutResolver: ILayoutResolver = {
is(route?: IERoute): boolean {
return isEmpty(route) || route!.layout === 'PRO_LAYOUT'
},
get({ routes, children, route, canAccess }: ILayoutProps) {
return (
<ProLayout routes={routes!} route={route} canAccess={canAccess}>
{children}
</ProLayout>
)
}
}

export const OPEN_LAYOUTS = [BlankResolver]
export const AUTH_REQUIRED_LAYOUTS = [ProLayoutResolver, BlankResolver]
const OPEN_LAYOUTS = [BlankResolver]
const AUTH_REQUIRED_LAYOUTS = [ProLayoutResolver, BlankResolver]

export function resolveOpenPage({ routes, children, route, canAccess }: ILayoutProps) {
const layout = OPEN_LAYOUTS.find(r => r.is(route))
if (isNotEmpty<ILayoutResolver>(layout)) {
return layout.get({ routes: routes!, children, route, canAccess })
const resolver = OPEN_LAYOUTS.find(r => r.is(route))
if (isNotEmpty<ILayoutResolver>(resolver)) {
return resolver.get({ routes: routes!, children, route, canAccess })
}
throw new Error(`no proper layout found for ${route!.path}, please check your code`)
}

export function resolveAuthRequiredPage({ routes, children, route, canAccess }: ILayoutProps) {
const layout = AUTH_REQUIRED_LAYOUTS.find(r => r.is(route))
if (isNotEmpty<ILayoutResolver>(layout)) {
return layout.get({ routes: routes!, children, route, canAccess })
const resolver = AUTH_REQUIRED_LAYOUTS.find(r => r.is(route))
if (isNotEmpty<ILayoutResolver>(resolver)) {
return resolver.get({ routes: routes!, children, route, canAccess })
}
throw new Error(`no proper layout found for ${route!.path}, please check your code`)
}
4 changes: 2 additions & 2 deletions generators/app/templates/src/models/useLoginModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function useLoginModel() {
const [currentToken, setCurrentToken] = useState(getToken())
const [isRememberme, setCurrentRememberme] = useState(!!getRememberme())

const initBackgroud = useCallback(() => {
const initBackground = useCallback(() => {
loadScript('/bg/particles.js', 'particlesJS').then(particlesJS => {
particlesJS.load('bg-animate', '/bg/particlesjs-config.json')
})
Expand Down Expand Up @@ -47,7 +47,7 @@ export default function useLoginModel() {
)

return {
initBackgroud,
initBackground,
currentToken,
isRememberme,
toggleRememberme,
Expand Down
6 changes: 3 additions & 3 deletions generators/app/templates/src/pages/o/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import styles from './index.less'

function Login() {
const { initialState } = useModel('@@initialState')
const { initBackgroud } = useModel('useLoginModel', m => pick(m, 'initBackgroud'))
const { initBackground } = useModel('useLoginModel', m => pick(m, 'initBackground'))
const { formatMessage } = useIntl()

useEffect(() => {
initBackgroud()
}, [initBackgroud])
initBackground()
}, [initBackground])

if (isNotEmpty(initialState) && !isString(initialState)) {
return <Redirect to="/" />
Expand Down
12 changes: 2 additions & 10 deletions generators/app/templates/src/types/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,13 @@ export interface IERoute extends IRoute {
export interface ILayoutProps {
children: JSX.Element
route: IERoute
location: ILocation
match: any
history: History
}

export interface IResolverOptions {
routes: IERoute[]
children: JSX.Element
route: IERoute
routes?: IERoute[]
canAccess: boolean
}

export interface ILayoutResolver {
is(route?: IERoute): boolean
get(options: IResolverOptions): JSX.Element
get(options: ILayoutProps): JSX.Element
}

export interface ILocation extends Location {
Expand Down

0 comments on commit 327c095

Please sign in to comment.