用的是umi 2.x
,寫起來挺舒服;順帶完善了上一版本後臺的一些細節問題,功能等javascript
umijs
相似create-react-app
, 也是一套方案的集合體,亮點不少.能夠具體官網去看css
nuxtjs
既視感)dva(基於redux+redux-saga的封裝方案)
:寫起來有vuex
的感受;主要記錄我在過程當中遇到的問題及解決的姿式,技術棧 antd 3.11.x
+ umi 2.x
+ react 16.7
html
關於momentvue
爲何說另類..就是原生日期API
結合moment
,由於咱們接口須要傳遞時間戳,而是不帶毫秒級的;java
並且時間必須爲當天的凌晨00:00:00
開始,結束時間到操做的此刻(直接new Date().getTime()
就是此刻);react
// 會直接返回你設置時間的時間戳
new Date().setHours(0, 0, 0, 0)
// 凌晨`00:00:00`
moment(new Date().setHours(0, 0, 0, 0))
// 近七天
moment(new Date().setHours(0, 0, 0, 0) - 7 * 24 * 3600000)
// 月初
moment().startOf('month')
複製代碼
轉成unix stamp(服務器經常使用的時間戳規格)
,調用moment().unix()
便可;webpack
如果不控制到凌晨00:00:00
這種,web
日期能夠直接用moment
的add
方法日後推導,subtract
往前推導,支持日/周/月/年vuex
antd
的日期組件數據庫
置空用null
是容許的,其餘的話須要轉成moment
對象,控件獲取的值默認就是moment
對象
props.children
的改造,添加樣式亦或者事件!在封裝一些組件的過程,我用了React.Fragment(<></>: 簡寫)
來保證組件同級並列
有些必須須要props.children
帶上一些屬性或者樣式來保證我想要的效果.
一開始無解, 由於Fragement簡寫的姿式
無法props
,那也就是說沒作寫成高階;
找了下官方文檔,發現有這麼兩個API
:
React Element
)的方法,與常規數組用法相似,只是參數不同這是上篇文章用到的部份內容,須要改造傳遞進來的按鈕,給添加樣式
// 構建
// 克隆子組件而且添加本身要添加的特性
const PropsBtn = React.Children.map(this.props.children, child =>
React.cloneElement(child, {
style: {
marginLeft: 8,
},
})
);
// 渲染
{PropsBtn ? <>{PropsBtn}</> : null} 複製代碼
memoize-one
來改善性能能夠緩存一樣參數的結果集,很是適用於遞歸這類的函數處理,大大減小計算的壓力;
也能用於React
這類,是否有必要從新setState
, 第二個參數支持比較,官方推薦用lodash
去深度比較
HOC
的組件最簡單粗暴的方法就是用變量緩存,而後直接返回組件,好比我這邊文章就用了;
React 折騰記 - (9) 基於Antd+react-router-breadcrumbs-hoc封裝一個小巧的麪包屑組件
在layouts
裏面分別寫對應的佈局,而後由一個鑑權組件去斷定是否容許進入,好比
/src/layout/index.js
import React from 'react';
import withRouter from 'umi/withRouter';
// 鑑權組件, 我寫了webpack alias
import Authorized from 'components/Authorized';
// 佈局組件
import EnranceLayout from './EntranceLayout';
import AdminLayout from './AdminLayout';
// 中文地區時間轉換引入
import moment from 'moment';
import 'moment/locale/zh-cn';
// 路由動效
import { TransitionGroup, CSSTransition } from 'react-transition-group';
// 頁面標題
import { Helmet } from 'react-helmet';
import { getDocumentTitle } from 'components/Sidebar/RouterTree';
moment.locale('zh-cn');
export default withRouter(props => {
const {
location: { pathname },
location,
} = props;
// 根據路由尋址,再結合鑑權來斷定是否容許進入,根據您自身的業務進行調整
if (pathname.indexOf('/entrance') === -1) {
if (pathname.indexOf('/editor') !== -1) {
return (
<Authorized> <Helmet> <title>{getDocumentTitle(pathname)}</title> </Helmet> <TransitionGroup> <CSSTransition key={location.key} classNames="spread" timeout={1000}> {props.children} </CSSTransition> </TransitionGroup> </Authorized>
);
}
return (
<AdminLayout> <Helmet> <title>{getDocumentTitle(pathname)}</title> </Helmet> <TransitionGroup> <CSSTransition key={location.key} classNames="spread" timeout={1000}> {props.children} </CSSTransition> </TransitionGroup> </AdminLayout>
);
}
return (
<EnranceLayout> <TransitionGroup> <CSSTransition key={location.key} classNames="spread" timeout={1000}> {props.children} </CSSTransition> </TransitionGroup> </EnranceLayout>
);
});
複製代碼
全局的放在src/models
目錄,其餘的page
級別推薦直接model.js
,官方說會自下往上尋找;
是根據namespace
來區分的..不容許存在同名的namespace
;
如果要開啓umi
的model
動態引入, page
級別不容許調用其餘page
的model
,否則會報錯,初始化找不到的!!!
因此全局性放在全局更爲合適,固然你不須要動態引入的話,頁面間跨調是容許的..我目前是這麼作;
pages
目錄下的文件或者目錄不自動生成對應可訪問的page
默認在page
目錄下,除了部分特殊的文件(好比官方本身過濾的models
),都會自動產生可訪問的頁面,
也就是說文件會被當作路由組件;
屏蔽的話, 打開項目的配置文件.umirc.js
const path = require('path');
// ref: https://umijs.org/config/
export default {
plugins: [
// ref: https://umijs.org/plugin/umi-plugin-react.html
[
'umi-plugin-react',
{
antd: true, // 默認引入antd
dva: { // 啓用引入dva
immer: true,
dynamicImport: false, // models 動態引入關閉
hmr: true,
},
dynamicImport: false, // 組件切割動態引入
title: '聲兮後臺管理系統',
dll: true,
routes: { // 此處用正則忽略不想產生路徑的文件或者目錄!!!
exclude: [/model\.js/, /models\//, /services(\/|\.js)?/, /components\//],
},
hardSource: true,
locale: {},
},
],
],
};
複製代碼
const path = require('path');
// ref: https://umijs.org/config/
export default {
plugins: [
alias: {
'@': path.resolve(__dirname, './src'),
models: path.resolve(__dirname, './src/models'),
components: path.resolve(__dirname, './src/components'),
utils: path.resolve(__dirname, './src/utils'),
services: path.resolve(__dirname, './src/services'),
assets: path.resolve(__dirname, './src/assets'),
},
proxy: {
'/api/web': {
target: 'http://stagapi.xxxx.com',
changeOrigin: true,
secure: false,
// pathRewrite: { '^/api': '/' },
},
},
};
複製代碼
dva
的dispatch
後setState
// dispatch返回的是一個promise,直接then便可,
// 傳入callback 姿式無效(對於setState)
this.props.dispatch({
type: 'appuser/batchItem',
payload: {
batchType: 2,
userIdList: userIdList,
},
})
.then(res => {
this.setState({
selectedRowKeys: [],
});
message.success(`批量封號,操做成功!`);
});
}}
複製代碼
preloading
就是react代碼沒加載以前,顯示的區域塊,
目前的作法就是自定義模板文件,放在react渲染塊內部,在解析代碼渲染完畢會被替換掉
效果以下
src/pages/document.ejs
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>xx管理後臺</title>
<style> .preloadLoading{ position:fixed; left:0; top:0; width:100%; height:100%; display:flex; justify-content:center; align-items:center; } @-webkit-keyframes square-animation { 0% { left: 0; top: 0; } 10.5% { left: 0; top: 0; } 12.5% { left: 32px; top: 0; } 23% { left: 32px; top: 0; } 25% { left: 64px; top: 0; } 35.5% { left: 64px; top: 0; } 37.5% { left: 64px; top: 32px; } 48% { left: 64px; top: 32px; } 50% { left: 32px; top: 32px; } 60.5% { left: 32px; top: 32px; } 62.5% { left: 32px; top: 64px; } 73% { left: 32px; top: 64px; } 75% { left: 0; top: 64px; } 85.5% { left: 0; top: 64px; } 87.5% { left: 0; top: 32px; } 98% { left: 0; top: 32px; } 100% { left: 0; top: 0; } } @keyframes square-animation { 0% { left: 0; top: 0; } 10.5% { left: 0; top: 0; } 12.5% { left: 32px; top: 0; } 23% { left: 32px; top: 0; } 25% { left: 64px; top: 0; } 35.5% { left: 64px; top: 0; } 37.5% { left: 64px; top: 32px; } 48% { left: 64px; top: 32px; } 50% { left: 32px; top: 32px; } 60.5% { left: 32px; top: 32px; } 62.5% { left: 32px; top: 64px; } 73% { left: 32px; top: 64px; } 75% { left: 0; top: 64px; } 85.5% { left: 0; top: 64px; } 87.5% { left: 0; top: 32px; } 98% { left: 0; top: 32px; } 100% { left: 0; top: 0; } } @-webkit-keyframes hue-rotate { 0% { -webkit-filter: hue-rotate(0deg); filter: hue-rotate(0deg); } 100% { -webkit-filter: hue-rotate(360deg); filter: hue-rotate(360deg); } } @keyframes hue-rotate { 0% { -webkit-filter: hue-rotate(0deg); filter: hue-rotate(0deg); } 100% { -webkit-filter: hue-rotate(360deg); filter: hue-rotate(360deg); } } .loading { position: relative; width: 96px; height: 96px; -webkit-transform: rotate(45deg); transform: rotate(45deg); -webkit-animation: hue-rotate 10s linear infinite both; animation: hue-rotate 10s linear infinite both; } .loading__square { position: absolute; top: 0; left: 0; width: 28px; height: 28px; margin: 2px; border-radius: 2px; background: #07a; background-image: -webkit-linear-gradient(45deg, #fa0 40%, #0c9 60%); background-image: linear-gradient(45deg, #fa0 40%, #0c9 60%); background-image: -moz-linear-gradient(#fa0, #fa0); background-size: cover; background-position: center; background-attachment: fixed; -webkit-animation: square-animation 10s ease-in-out infinite both; animation: square-animation 10s ease-in-out infinite both; } .loading__square:nth-of-type(0) { -webkit-animation-delay: 0s; animation-delay: 0s; } .loading__square:nth-of-type(1) { -webkit-animation-delay: -1.42857s; animation-delay: -1.42857s; } .loading__square:nth-of-type(2) { -webkit-animation-delay: -2.85714s; animation-delay: -2.85714s; } .loading__square:nth-of-type(3) { -webkit-animation-delay: -4.28571s; animation-delay: -4.28571s; } .loading__square:nth-of-type(4) { -webkit-animation-delay: -5.71429s; animation-delay: -5.71429s; } .loading__square:nth-of-type(5) { -webkit-animation-delay: -7.14286s; animation-delay: -7.14286s; } .loading__square:nth-of-type(6) { -webkit-animation-delay: -8.57143s; animation-delay: -8.57143s; } .loading__square:nth-of-type(7) { -webkit-animation-delay: -10s; animation-delay: -10s; } </style>
</head>
<body>
<div id="root">
<div class="preloadLoading">
<div class='loading'>
<div class='loading__square'></div>
<div class='loading__square'></div>
<div class='loading__square'></div>
<div class='loading__square'></div>
<div class='loading__square'></div>
<div class='loading__square'></div>
<div class='loading__square'></div>
</div>
</div>
</div>
</body>
</html>
複製代碼
首先得本身維護一份靜態路由表,相似vue
或者react-router@3
那種,
結合@withRouter
拿到pathname
傳入到靜態路由表遍歷
(這裏就能夠用到上面說的memoize-one來提升性能),
效果以下
姿式以下
用react-helmet
來實現title
的替換,這貨不只僅能夠替換title
還能替換meta
這些
參考上面的問題 ==> umi 約定式基礎鑑權 ,這裏就有用到
就是縮小的時候隱藏部分子菜單,這個問題在我作側邊欄變水平的時候遇到.我縮小到ipad
的尺寸
會溢出,用了常規的法子,就正常了,就是style
那裏設置一個最大寬度或者寬度
至於風格變化是由於antd
內置了兩套風格
<Menu
style={{ maxWidth: '100%', flex: 1 }}
subMenuOpenDelay={0.3}
theme={theme ? 'dark' : 'light'}
mode={mode ? 'horizontal' : 'inline'}
openKeys={openKeys}
selectedKeys={selectedKeys}
onOpenChange={this.onOpenChange}
>
複製代碼
固然Logo
組件這些確定是你本身拿了狀態去變化的,還有包裹的父級區域的樣式
目前不作配置保存,想作保存的,寫在localStorage
不失爲一個好法子,不必寫到數據庫,都是本身人用
效果以下
項目沒有用到antd pro
這個模板(太臃腫),本身寫比較實在
有新的且以爲有點意義的問題我會陸續更新,有不對之處請留言,會及時修正..謝謝閱讀