npm install rc-queue-anim --save # npm
yarn add rc-queue-anim # Yarn
注意一点,被QueueAnim包裹的dom元素,一定要加入key关键字,不然是不会起效果的
import React, { Component } from 'react'
import Style from './style.styl'
import PropTypes from 'prop-types';
import { inject, observer } from 'mobx-react'
import { withRouter } from 'react-router-dom'
import QueueAnim from 'rc-queue-anim';
@inject('homePageStore')
@withRouter
@observer
export default class CommentHomePage extends Component {
constructor (props) {
super(props)
this.store = props.homePageStore
}
componentDidMount() {
}
componentWillUnmount() {
}
out = () => {
localStorage.clear();
location.reload()
// this.props.history.push('login')
};
render() {
return (
<QueueAnim
delay={150}
type={['right', 'left']}
ease={['easeOutQuart', 'easeInOutQuart']}>
<div key="a" className={Style['home-wrapper']} onClick={() => {this.out()}}>666</div>
</QueueAnim>
)
}
}
CommentHomePage.propTypes = {
homePageStore: PropTypes.object,
history: PropTypes.object
};
npm install moment --save # npm
yarn add moment # Yarn
import React, { memo } from 'react'
import zhCN from 'antd/es/locale/zh_CN'
import { ConfigProvider } from 'antd'
import Router from 'src/router'
import moment from 'moment'//在原有的基础上加上下面三行代码
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
const App: React.FC = () => {
return (
<ConfigProvider locale={zhCN}>
<Router />
</ConfigProvider>
)
}
export default memo(App)
``
import CryptoJS from 'crypto-js';
class CryptoFile {
constructor () {
// 秘钥
this.key = CryptoJS.enc.Utf8.parse('CRYPTOJSKEY00000'); // 16位
this.iv = CryptoJS.enc.Utf8.parse('CRYPTOJSKEY00000');
this.CRYPTOJSKEY = 'CRYPTOJSKEY'
}
// 加密
encrypt(word) {
let srcS = CryptoJS.enc.Utf8.parse(word);
let encrypted = CryptoJS.AES.encrypt(srcS, this.key, { iv: this.iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
return encrypted.ciphertext.toString().toUpperCase();
}
// 解密
decrypt(word) {
let encryptedHexStr = CryptoJS.enc.Hex.parse(word);
let srcS = CryptoJS.enc.Base64.stringify(encryptedHexStr);
let decrypt = CryptoJS.AES.decrypt(srcS, this.key, { iv: this.iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
// eslint-disable-next-line no-debugger
// debugger
let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}
}
export default new CryptoFile()
import crypto from './crypto';
const $http = (url = '', data = {}, type = 'GET', _config = {}) => new Promise((resolve, reject) => {
type = type.toUpperCase();
const config = Object.assign(_config, {
method: type,
url: url
});
// get请求走正常
if (['GET'].includes(type)) {
config.params = crypto.encrypt(JSON.stringify(data));
} else { // post增加请求头
Object.assign(config, {
headers: {
// 'Content-Type': 'multipart/form-data',
'Content-Type': 'application/json',
'token': localStorage.getItem('token')
}
});
// 封装Data => FormData
// const formdata = new FormData();
// for (let key in data) {
// formdata.append(key, data[key]);
// }
config.data = crypto.encrypt(JSON.stringify(data));
// console.log('解密', config.data, JSON.parse(crypto.decrypt(config.data)))
}
配置路由数组,用来存储路由数据
// 配置路由鉴权
const routes = [
{
path: '/login',
exact: true,
component: LoginPage
},
{
path: '/admin',
component: Tacos,
requiresAuth: true,
children: [
{
path: '/admin/home',
exact: true,
requiresAuth: true,
component: homePage
},
{
path: '/admin/info',
exact: true,
requiresAuth: true,
component: InfoPage
},
]
},
{
path: '/power',
component: Tacos,
requiresAuth: true,
children: [
{
path: '/power/test',
exact: true,
requiresAuth: true,
component: testPage
},
]
},
{
path: '/error',
exact: true,
requiresAuth: true,
component: NotFound
}
];
导入组件
import { Layout, Menu, Breadcrumb } from 'antd';
const { SubMenu } = Menu;
const { Header, Content, Sider } = Layout;
配置路由
export default class AppRoute extends Component {
render () {
let authPath = '/login';
return (
<Router>
<Switch>
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} {...route} />
))}
<Redirect to={{ pathname: authPath }} />
</Switch>
</Router>
);
}
}
// A special wrapper for <Route> that knows how to
// handle "sub"-routes by passing them in a `routes`
// prop to the component it renders.
function RouteWithSubRoutes(route) {
let authPath = '/login';
return (
<Route
path={route.path}
render={(props) => {
let token = localStorage.getItem('token');
// 当已经登录,并且跳转路由是login,则进入首页
if (route.path === authPath && token) {
console.log('登录页面强制跳转首页')
return <Redirect to={{ pathname: '/admin/home' }} />
}
// 不需要验证登录的页面,当前为登录状态,路由等于login页面,则默认放行
if (!route.requiresAuth || token || route.path === authPath) {
console.log('进入正常页面', route)
return <route.component {...props} routes={route.children} />
}
console.log('登录失效,重定向登录页')
return <Redirect to={{ pathname: authPath }} />
}}
/>
);
}
function Tacos({ routes }) {
return (
<Layout>
<NavBar name={'nav'}/>
<Layout>
<Sider width={256} style={{ background: 'white' }}>
<MenuBar name={'menu'}/>
</Sider>
<Layout style={{width: 'calc(100vw - 256px)', height: 'calc(100vh - 50px)'}}>
<Switch>
{routes.map((route, i) => (
<RouteWithSubRoutes key={i} {...route} />
))}
<Redirect to={{ pathname: routes[0].path }} />
</Switch>
</Layout>
</Layout>
</Layout>
);
}
Tacos.propTypes = {
routes: PropTypes.array,
};
npm i -S axios
在utils文件下新建Http.js
import axios from 'axios';
import React, { Component } from 'react';
import webConfig from './web_config'
import ReactDOM from 'react-dom';
import { message, Spin } from 'antd';
// 设置请求路径
axios.defaults.baseURL = webConfig.rootUrl;
axios.defaults.withCredentials = true;
// 设置post请求头
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
// 当前正在请求的数量
let requestCount = 0
// 全局显示loading
function showLoading () {
if (requestCount === 0) {
let dom = document.createElement('div')
dom.setAttribute('id', 'loading')
dom.style.position = 'fixed';
dom.style.top = '0px';
dom.style.left = '0px';
dom.style.background = 'rgba(0, 0, 0, 0.4)';
dom.style.display = 'flex';
dom.style.justifyContent = 'center';
dom.style.alignItems = 'center';
dom.style.width = '100vw';
dom.style.height = '100vh';
document.body.appendChild(dom)
ReactDOM.render(<Spin tip="数据请求中,请稍后..." size="large"/>, dom)
}
requestCount++
}
// 隐藏loading
function hideLoading () {
requestCount--
if (requestCount === 0) {
document.body.removeChild(document.getElementById('loading'))
}
}
// 请求头中设置isLoading = false, 则可以取消全局请求loading效果
// 请求前拦截
axios.interceptors.request.use((config) => {
// 开启全局loading
if (config.headers.isLoading !== false) {
showLoading()
}
return config
}, (err) => {
// 取消全局请求loading
if (err.config.headers.isLoading !== false) {
hideLoading()
}
return Promise.reject(err)
})
// 返回后拦截
axios.interceptors.response.use((res) => {
// 取消全局loading
if (res.config.headers.isLoading !== false) {
hideLoading()
}
return res
}, (err) => {
// 取消全局loading
if (err.config.headers.isLoading !== false) {
hideLoading()
}
// 可以配置访问异常的回调提示
if (err.message === 'Network Error') {
message.warning('网络连接异常!')
}
return Promise.reject(err)
})
const $http = (url = '', data = {}, type = 'GET', _config = {}) => new Promise((resolve, reject) => {
type = type.toUpperCase();
const config = Object.assign(_config, {
method: type,
url: url
});
// get请求走正常
if (['GET'].includes(type)) {
config.params = data;
} else { // post增加请求头,这里看数据格式,如果是json则需要自己更改
Object.assign(config, {
headers: {
// 'Content-Type': 'application/json', // 这里自己协商,用formdata还是json
'Content-Type': 'multipart/form-data',
'token': localStorage.getItem('token')
}
});
// 封装Data => FormData
const formdata = new FormData();
for (let key in data) {
formdata.append(key, data[key]);
}
config.data = formdata;
}
axios(config).then((response) => {
// 登录失败,封装以下,当接口报错后,返回报错接口,报错参数
if (response && (response.data.code !== 200)) {
console.error('接口传参报错', response.config.url, '参数', JSON.parse(convert_FormData_to_json2(response.config.data)))
message.info(response.data.msg);
resolve(response && response.data);
} else {
resolve(response && response.data);
}
})
.catch((err) => {
// 服务端报错,则返回报错接口
console.error('接口服务报错', err.config.url)
reject(err);
});
})
let convert_FormData_to_json2 = (formData) => {
let objData = {};
formData.forEach((value, key) => objData[key] = value);
return JSON.stringify(objData);
}
// 配置的是当code返回200,则直接返回数据,这个前端可以直接获取数据,可以少写一个if判断
export const $getData = async (url, data, _config = {}) => {
let res = await $http(url, data, 'GET', _config)
if (res.code === 200){
return Promise.resolve(res.data)
} else {
return Promise.reject(res.data)
}
// 如果改成这种,请求会无条件返回,需要在调用接口的地方,再做判断
// return Promise.resolve(res)
}
// 同上
export const $postData = async (url, data, _config = {}) => {
let res = await $http(url, data, 'POST', _config)
if (res.code === 200){
return Promise.resolve(res)
} else {
return Promise.reject(res)
}
// 同上
// return Promise.resolve(res)
}
npm i -S cssnano postcss-viewport-units postcss-aspect-ratio-mini postcss-px-to-viewport-opt postcss-write-svg postcss-preset-env
// 在文件头部导入下载的包
const postcssAspectRatioMini = require('postcss-aspect-ratio-mini');
const postcssPxToViewport = require('postcss-px-to-viewport-opt');
const postcssWriteSvg = require('postcss-write-svg');
const postcssPresetEnv = require('postcss-preset-env')
const postcssViewportUnits = require('postcss-viewport-units');
const cssnano = require('cssnano');
// 找到postcss部分,在下面加入配置项
{
// Options for PostCSS as we reference these options twice
// Adds vendor prefixing based on your specified browser support in
// package.json
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebook/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
// Adds PostCSS Normalize as the reset css with default options,
// so that it honors browserslist config in package.json
// which in turn let's users customize the target behavior as per their needs.
postcssNormalize(),
// 在这个位置加入VW需要的配置的
postcssAspectRatioMini({}),
postcssPxToViewport({
viewportWidth: 1920, // (Number) The width of the viewport.
viewportHeight: 1080, // (Number) The height of the viewport.
unitPrecision: 3, // (Number) The decimal numbers to allow the REM units to grow to.
viewportUnit: 'vw', // (String) Expected units.
selectorBlackList: ['.ignore', '.hairlines', '.antd'], // (Array) The selectors to ignore and leave as px.
minPixelValue: 1, // (Number) Set the minimum pixel value to replace.
mediaQuery: false, // (Boolean) Allow px to be converted in media queries.
exclude: /(\/|\\)(node_modules)(\/|\\)/
}),
postcssWriteSvg({
utf8: false
}),
postcssPresetEnv({}),
postcssViewportUnits({}),
cssnano({
'cssnano-preset-advanced': {
zindex: false,
autoprefixer: false
}
}),
// 在这个位置加入VW需要的配置
],
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
npm i -S mobx mobx-react
import { configure } from 'mobx';
import HomePage from '../views/home_page/store'
import NavBar from '../component/nav_bar/store'
import MenuBar from '../component/menu_bar/store'
import InfoPage from '../views/info_page/store'
import LoginPage from '../views/login_page/store'
import NotPage from '../views/404_page/store'
configure({ enforceActions: 'always' });
class AppStore {
constructor () {
this.homePageStore = new HomePage(this);
this.navBarStore = new NavBar(this);
this.menuBarStore = new MenuBar(this);
this.infoStore = new InfoPage(this);
this.loginStore = new LoginPage(this);
this.notStore = new NotPage(this)
}
}
export default new AppStore()
npm i -D stylus stylus-loader
找到rules数组下的oneOf,看到这里都是配置是css,js等文件的loader,在下面增加
{ // 配置 stylus
test: stylusRegex,
exclude: stylusModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: true, // 设置模块化
},
'stylus-loader'
),
sideEffects: true,
},
npm i -D react-router-dom
在AppRouter.js 配置
在文件页面配置,并使用witherRouter注解
npm i -S antd
This project was bootstrapped with Create React App.
In the project directory, you can run:
Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
Launches the test runner in the interactive watch mode.
See the section about running tests for more information.
Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.
Note: this is a one-way operation. Once you eject, you can’t go back!
If you aren’t satisfied with the build tool and configuration choices, you can eject at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except eject will sti