用的是umi 2.x
,写起来挺舒服;顺带完善了上一版本后台的一些细节问题,功能等
umijs
类似create-react-app
, 也是一套方案的集合体,亮点很多.可以具体官网去看
nuxtjs
既视感)dva(基于redux+redux-saga的封装方案)
:写起来有vuex
的感觉;主要记录我在过程中遇到的问题及解决的姿势,技术栈 antd 3.11.x
+ umi 2.x
+ react 16.7
关于moment
为什么说另类..就是原生日期API
结合moment
,因为我们接口需要传递时间戳,而是不带毫秒级的;
而且时间必须为当天的凌晨00:00:00
开始,结束时间到操作的此刻(直接new Date().getTime()
就是此刻);
// 会直接返回你设置时间的时间戳
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()
即可;
若是不控制到凌晨00:00:00
这种,
日期可以直接用moment
的add
方法往后推导,subtract
往前推导,支持日/周/月/年
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': '/' },
},
},
};
复制代码
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
这个模板(太臃肿),自己写比较实在
有新的且觉得有点意义的问题我会陆续更新...继续写小程序的需求去..
有不对之处请留言,会及时修正..谢谢阅读