Skip to content

Commit

Permalink
feat: dynamic theme supported
Browse files Browse the repository at this point in the history
  • Loading branch information
leftstick committed Jul 20, 2020
1 parent a865408 commit df9c10a
Show file tree
Hide file tree
Showing 23 changed files with 284 additions and 9,042 deletions.
2 changes: 1 addition & 1 deletion docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports = {
{
title: 'Guide',
collapsable: false,
children: ['', 'route', 'layout', 'locale', 'data', 'packaging']
children: ['', 'route', 'layout', 'locale', 'data', 'theme', 'packaging']
}
]
}
Expand Down
Binary file modified docs/.vuepress/public/demo.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ features:
details: 便捷的多语言切换功能
- title: 数据管理
details: 不同于 redux 的数据管理体验,让 hooks 带你high个够
- title: 主题切换
details: 便捷的动态主题切换功能,让你的应用从此绚丽多彩
- title: 打包
details: 内置 `.zip`、`docker` 镜像两种打包风格,让你的业务推动更顺滑
footer: MIT Licensed | Copyright © 2020-present Howard.Zuo
Expand Down
3 changes: 2 additions & 1 deletion docs/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

本脚手架使用了 [umi@3](https://umijs.org) + [typescript](http://www.typescriptlang.org), 以及 [antd@4](https://ant.design)。对这三样完全不熟悉的朋友,强烈建议先做了解之后再来。(再见)

其次,本脚手架没有使用 [preset-react](https://umijs.org/plugins/preset-react),而是单独使用了 [plugin-access](https://umijs.org/plugins/plugin-access)[plugin-antd](https://umijs.org/plugins/plugin-antd)[plugin-initial-state](https://umijs.org/plugins/plugin-initial-state)[plugin-locale](https://umijs.org/plugins/plugin-locale)[plugin-model](https://umijs.org/plugins/plugin-model)[plugin-request](https://umijs.org/plugins/plugin-request)。并通过内置的 [路由系统](/guide/route.md)[布局系统](/guide/layout.md) 来完成权限路由和布局选择的功能。目的是为开发者提供一个扩展性更强的 code base。
其次,本脚手架没有使用 [preset-react](https://umijs.org/plugins/preset-react),而是单独使用了 [plugin-access](https://umijs.org/plugins/plugin-access)[plugin-antd](https://umijs.org/plugins/plugin-antd)[plugin-initial-state](https://umijs.org/plugins/plugin-initial-state)[plugin-locale](https://umijs.org/plugins/plugin-locale)[plugin-model](https://umijs.org/plugins/plugin-model)[plugin-request](https://umijs.org/plugins/plugin-request)[umi-plugin-antd-theme](https://github.com/chenshuai2144/umi-plugin-antd-theme)。并通过内置的 [路由系统](/guide/route.md)[布局系统](/guide/layout.md) 来完成权限路由和布局选择的功能。目的是为开发者提供一个扩展性更强的 code base。

所以对上述使用到的插件不熟悉的朋友,也需要先了解之后再往下看,再见。

Expand Down Expand Up @@ -87,4 +87,5 @@
- [布局](/guide/layout.html)
- [国际化](/guide/locale.html)
- [数据](/guide/data.html)
- [主题](/guide/theme.html)
- [打包](/guide/packaging.html)
64 changes: 64 additions & 0 deletions docs/guide/theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# 主题

脚手架使用 [umi-plugin-antd-theme](https://github.com/chenshuai2144/umi-plugin-antd-theme) 来提供动态主题切换功能。

首先,多主题由 `config/theme.config.json` 定义,内容如下:

```json
{
"theme": [
{
"fileName": "dust.css",
"modifyVars": {
"@primary-color": "#F5222D"
}
},
{
"fileName": "cyan.css",
"modifyVars": {
"@primary-color": "#13C2C2"
}
}
],
"min": false,
"isModule": true,
"ignoreAntd": false,
"ignoreProLayout": false,
"cache": false
}
```

其中:

`fileName` 是每个主题最终输出的文件名,请务必保持命名通俗易懂,如上述所示,文件名去掉 `.css`,就是主题名。

`modifyVars` 就是 `antd` 的 less 变量,同 [theme](https://umijs.org/config#theme)

## application-level 定制

你也可以通过在 `src/global.less` 中定义应用级别的 less 变量,如下:

```less
// 默认主题
body {
--title-color: #4f4f4f;
}

// 主题 dust
body.body-wrap-dust {
--title-color: #8f1256;
}

// 主题 cyan
body.body-wrap-cyan {
--title-color: #5cdc5a;
}
```

然后,通过如下方式,在你的组件中,引用定义在 `src/global.less` 中变量就可以了:

```less
.desc {
color: var(--title-color);
}
```
23 changes: 23 additions & 0 deletions generators/app/templates/config/theme.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"theme": [
{
"key": "dust",
"fileName": "dust.css",
"modifyVars": {
"@primary-color": "#F5222D"
}
},
{
"key": "cyan",
"fileName": "cyan.css",
"modifyVars": {
"@primary-color": "#13C2C2"
}
}
],
"min": false,
"isModule": true,
"ignoreAntd": false,
"ignoreProLayout": false,
"cache": false
}
23 changes: 13 additions & 10 deletions generators/app/templates/package.json.vm
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,29 @@
]
},
"lint-staged": {
"*.js": "eslint --max-warnings=0"
"*.{j,t}sx?": "eslint --max-warnings=0"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@commitlint/cli": "^9.1.1",
"@commitlint/config-conventional": "^9.1.1",
"@types/classnames": "^2.2.10",
"@types/jest": "^26.0.5",
"@types/js-cookie": "^2.2.6",
"@umijs/plugin-access": "^2.3.1",
"@umijs/plugin-antd": "^0.5.1",
"@umijs/plugin-antd": "^0.7.0",
"@umijs/plugin-initial-state": "^2.2.1",
"@umijs/plugin-locale": "^0.7.0",
"@umijs/plugin-model": "^2.3.1",
"@umijs/plugin-request": "^2.3.2",
"@umijs/plugin-locale": "^0.9.0",
"@umijs/plugin-model": "^2.5.1",
"@umijs/plugin-request": "^2.4.1",
"@umijs/test": "^3.2.10",
"husky": "^4.2.5",
"lint-staged": "^10.2.6",
"lint-staged": "^10.2.11",
"react-coding-style": "^1.1.0",
"umi": "^3.2.2"
"umi": "^3.2.10",
"umi-plugin-antd-theme": "^2.1.2"
},
"dependencies": {
"@umijs/hooks": "^1.9.2",
"ahooks": "^2.2.0",
"classnames": "^2.2.6",
"js-cookie": "^2.2.1"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.size {
font-size: 20px;
}

.themeItem {
display: flex;
justify-content: flex-start;
align-items: center;
}

.img {
display: block;
width: 22px;
height: 17px;
margin-right: 2px;
}

.defaultBg {
background-color: #1890ff;
}

.dustBg {
background-color: #f5222d;
}

.cyanBg {
background-color: #13c2c2;
}

.defaultFont {
color: #1890ff;
}

.dustFont {
color: #f5222d;
}

.cyanFont {
color: #13c2c2;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useMemo, useEffect } from 'react'
import classnames from 'classnames'

import { Dropdown, Menu } from 'antd'
import { AppstoreOutlined } from '@ant-design/icons'
import { useLocalStorageState } from 'ahooks'

import styles from './index.less'

interface IThemeSwitchProps {
className?: string
}

enum Theme {
DEFAULT = 'default',
DUST = 'dust',
CYAN = 'cyan'
}

function changeTheme(nextTheme: Theme) {
let styleLink = document.getElementById('theme-style') as HTMLLinkElement
const body = document.getElementsByTagName('body')[0]

if (nextTheme === Theme.DEFAULT && styleLink) {
styleLink.remove()
const lastTheme = Array.from(document.body.classList).find(s => s.startsWith('body-wrap-'))
if (lastTheme) {
body.classList.remove(lastTheme)
}
return
}
if (styleLink) {
styleLink.href = `/theme/${nextTheme}.css` // 切换 antd 组件主题
body.className = `body-wrap-${nextTheme}` // 切换 app-level 的主题
return
}
styleLink = document.createElement('link')
styleLink.type = 'text/css'
styleLink.rel = 'stylesheet'
styleLink.id = 'theme-style'
styleLink.href = `/theme/${nextTheme}.css`
body.className = `body-wrap-${nextTheme}`
document.body.append(styleLink)
}

export default function ThemeSwitch({ className }: IThemeSwitchProps) {
const [theme, setTheme] = useLocalStorageState('umi-app-theme', Theme.DEFAULT)

const menu = useMemo(
() => (
<Menu
style={{ width: 150 }}
selectedKeys={[theme]}
onClick={e => {
setTheme(e.key as Theme)
changeTheme(e.key as Theme)
}}
>
<Menu.Item key={Theme.DEFAULT}>
<div className={classnames(styles.themeItem, styles.defaultFont)}>
<span role="img" className={classnames(styles.img, styles.defaultBg)} />
Default
</div>
</Menu.Item>
<Menu.Item key={Theme.DUST}>
<div className={classnames(styles.themeItem, styles.dustFont)}>
<span role="img" className={classnames(styles.img, styles.dustBg)} />
Dust
</div>
</Menu.Item>
<Menu.Item key={Theme.CYAN}>
<div className={classnames(styles.themeItem, styles.cyanFont)}>
<span role="img" className={classnames(styles.img, styles.cyanBg)} />
Cyan
</div>
</Menu.Item>
</Menu>
),
[setTheme, theme]
)

useEffect(() => {
if (theme !== Theme.DEFAULT) {
changeTheme(theme)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return (
<Dropdown overlay={menu} className={classnames(styles.size, className)}>
<div>
<AppstoreOutlined style={{ cursor: 'pointer' }} />
</div>
</Dropdown>
)
}
1 change: 1 addition & 0 deletions generators/app/templates/src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as LangSwitch } from './buttons/LangSwitch'
export { default as ThemeSwitch } from './buttons/ThemeSwitch'
export { default as MenuFoldButton } from './buttons/MenuFoldButton'
export { default as Exception403 } from './exception/403'
export { default as Exception404 } from './exception/404'
Expand Down
11 changes: 11 additions & 0 deletions generators/app/templates/src/global.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
body {
--title-color: #4f4f4f;
}

body.body-wrap-dust {
--title-color: #8f1256;
}

body.body-wrap-cyan {
--title-color: #5cdc5a;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Avatar, Dropdown, Menu } from 'antd'
import { LogoutOutlined, UserOutlined } from '@ant-design/icons'
import { useModel, useIntl, history } from 'umi'

import { MenuFoldButton, LangSwitch } from '@/components'
import { MenuFoldButton, LangSwitch, ThemeSwitch } from '@/components'
import { redirectToLogin } from '@/helpers'
import { IUser } from '@/types'

Expand Down Expand Up @@ -55,7 +55,8 @@ export default function NavigationBar({ sidebarCollapsed, onToggleSidebar }: INa
</div>
</Dropdown>

<LangSwitch className={styles.lang} />
<LangSwitch className={styles.navItem} />
<ThemeSwitch className={styles.navItem} />
</div>
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
h1 {
display: inline-block;
margin: 0 0 0 12px;
color: #fff;
color: @primary-color;
font-weight: 600;
font-size: 20px;
vertical-align: middle;
Expand Down Expand Up @@ -60,7 +60,7 @@
width: 150px;
}

.lang {
.navItem {
width: 40px;
display: flex;
justify-content: center;
Expand Down
4 changes: 2 additions & 2 deletions generators/app/templates/src/models/useAppModel.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useCallback } from 'react'
import { useSize } from '@umijs/hooks'
import { useSize } from 'ahooks'
import { matchPath } from 'umi'

import { flattenTree, isEmpty, isNotEmpty, isBoolean, isInvalidInitState, isString } from '@/helpers'
import { IERoute, IAccessState, IInvalidInitState } from '@/types'

export default function useAppModel() {
const [{ width, height }] = useSize(document.body)
const { width, height } = useSize(document.body)

const findMatchedRoute = useCallback((pathname: string, routes: IERoute[]) => {
return flattenTree(routes, r => r.routes)
Expand Down
2 changes: 1 addition & 1 deletion generators/app/templates/src/models/useProLayoutModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback } from 'react'
import { useLocalStorageState } from '@umijs/hooks'
import { useLocalStorageState } from 'ahooks'
import pathToRegexp from 'path-to-regexp'

import { flattenTree, isEmptyArray, isEmpty, isNotEmpty } from '@/helpers'
Expand Down
3 changes: 3 additions & 0 deletions generators/app/templates/src/pages/Profile/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.desc {
color: var(--title-color);
}

0 comments on commit df9c10a

Please sign in to comment.