一個toB的智能製造項目,分爲分析端和管理端。分析端涉及到各類圖表展現,經過時間範圍來控制顯示內容;管理端主要是大量表單&表格。(第一次正經用react進行開發,學習了一個星期就開工咯( ╯□╰ ))javascript
const path = require('path');
const { override, fixBabelImports, addLessLoader, addWebpackAlias, babelInclude ,useBabelRc } = require('customize-cra');
const TerserPlugin = require('terser-webpack-plugin');
function resolve(dir) {
return path.join(__dirname, '.', dir)
}
let addCustom = () => config => { //屏蔽.map.js文件,防止被讀到源碼
let optimization = {
minimizer: [
new TerserPlugin({
sourceMap: false
})
]
}
config.optimization.minimizer = optimization.minimizer;
return config;
}
module.exports = override(
fixBabelImports('import', { //按需引入antd
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {
// '@primary-color': '#1DA57A' ,
// '@link-color': '#1DA57A',
},
}),
addWebpackAlias({ //添加別名
'@': resolve('src'),
'components':path.resolve(__dirname,'src/components'),
'views': path.resolve(__dirname,'src/views'),
'layout': path.resolve(__dirname,'src/layout'),
'router': path.resolve(__dirname,'src/router'),
'api':path.resolve(__dirname,'src/api'),
'store': path.resolve(__dirname,'src/store'),
'assets': path.resolve(__dirname,'src/assets,'),
'mock': path.resolve(__dirname,'src/mock'),
'utils': path.resolve(__dirname,'src/utils')
}),
babelInclude([
path.resolve("src"),
]),
useBabelRc(), //配置裝飾器(mobx會用到)還須要.babelrc文件配合
addCustom()
);
複製代碼
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
複製代碼
const proxy = require('http-proxy-middleware');
const target = 'http://123.4.5.6:8080';
module.exports = function(app) {
app.use(proxy('/user', { target }))
app.use(proxy('/login', { target }))
app.use(proxy('/show', { target }))
app.use(proxy('/back', { target })))
};
複製代碼
import axios from "axios";
import qs from "qs"; //post請求時序列化
import { notification } from 'antd';
// http請求攔截器
axios.interceptors.request.use(
config => {
if (config.method.toUpperCase() === "GET") {
config.url =
config.url.indexOf("?") > 0
? config.url + "&clearCache=" + new Date().valueOf()
: config.url + "?clearCache=" + new Date().valueOf();
}
if (config.method.toUpperCase() === "POST") {
if (Object.prototype.toString.call(config.data) === "[object FormData]") {
console.log("數據類型", Object.prototype.toString.call(config.data));
} else {
config.data = qs.stringify(config.data); //序列化
config.headers["Content-Type"] = "application/x-www-form-urlencoded";
}
}
config.headers["Authorization"] = window.localStorage.getItem("token") ? window.localStorage.getItem("token") : '';
return config;
},
error => {
return Promise.reject(error);
}
);
// http響應攔截器
let loginTipLock = false;
axios.interceptors.response.use(
data => {
if (data.data["code"] && data.data["code"] === -2) {
window.location.hash = "#/login";
}
window.localStorage.setItem('token', data.headers.authorization) //token刷新機制
return data;
},
err => {
if (err && err.response) {
switch (err.response.status) {
case 400:
err.message = "請求錯誤";
break;
case 401:
err.message = "登陸已過時,請從新登陸!";
window.location.hash = "#/login"; //token過時機制
break;
case 403:
err.message = "拒絕訪問";
window.location.hash = "#/notAuth";
break;
case 404:
err.message = `請求地址出錯: ${err.response.config.url}`;
break;
case 408:
err.message = "請求超時";
break;
case 500:
err.message = "服務器內部錯誤";
break;
case 501:
err.message = "服務未實現";
break;
case 502:
err.message = "網關錯誤";
break;
case 503:
err.message = "服務不可用";
break;
case 504:
err.message = "網關超時";
break;
case 505:
err.message = "HTTP版本不受支持";
break;
default:
}
}
if (err.response.status === 401) {
if (!loginTipLock) { //避免同時多個請求都返回401時,彈出多個「未登陸」提示框
loginTipLock = true;
notification.info({
message: '提示',
description: err.message
})
setTimeout(function () {
loginTipLock = false;
}, 1000)
}
} else {
notification.error({
message: '出錯啦',
description: err.message
});
}
return Promise.reject(err);
}
);
export default axios;
----對請求結果作一個統一處理----
import axios from "./axios";
import { notification } from 'antd';
const $http = (url, method = "GET", data, config = {}) => {
const _config = Object.assign({ url, method, data }, config);
return axios(_config).then(res => {
if (res.status === 200) {
if (res.data.code === -1) {
notification.error({
message: '出錯啦',
description: res.data.msg
});
throw new Error("請求出錯啦");
}
return res;
}
});
};
export default {
/*註冊*/
signIn: ({ username, password }) => $http(`/user/register`, "POST", { username,password }),
/*登陸 */
login: ({ username, password }) => $http("/user/login", "POST", { username, password }),
/*上傳文件 */
uploadFile: file => $http( "/upload/csv", "POST", {}, {headers: { "Content-Type": "multipart/form-data" }, processData: false, cache: false, data: file }),
}
複製代碼
/* 定義 */
import React, { Component } from 'react'
import { withRouter } from 'react-router'
import { Route, Redirect } from 'react-router-dom'
import { inject, observer } from 'mobx-react';
@inject('appState')
@observer
class AuthorizedRoute extends Component {
render() {
const { component: Component, ...rest } = this.props;
const isLogin = !!JSON.parse(window.localStorage.getItem("userInfo"));
const userRole = JSON.parse(window.localStorage.getItem("userInfo")).role : 'user';
const path = this.props.path.substring(1);
const { authList } = this.props.appState;
return (
<Route {...rest} render={props => {
return isLogin
? (authList[path].includes(userRole) ? <Component {...props} /> : <Redirect to="/notAuth" />)
: <Redirect to="/login" />
}} />
)
}
}
export default withRouter(AuthorizedRoute);
/* 引用 */
const AuthorizedRoute = lazy( () => import('router/auth')); //react懶加載
class App extends Component{
render(){
return (
<div className="App">
<HashRouter>
<Suspense fallback={PageLoading}> //在Suspense組件中渲染lazy組件,咱們能夠在等待加載lazy組件時作優雅降級(如loading指示器)
<Switch>
<Redirect path='/' exact to="/show" />
<AuthorizedRoute path="/show" component={ShowHomeLayout} />
<AuthorizedRoute path="/back" component={BackHomeLayout} />
<Route path="/login" component={Login}></Route>
<Route path="/notAuth" component={NotAuth}></Route>
<Route component={NotFound} />
</Switch>
</Suspense>
</HashRouter>
</div>
);
}
}
export default App;
複製代碼
若是整個頁面的數據依賴一個id,那麼最好把id做爲路由的參數。css
緣由:
1.要考慮用戶複製當前url在新窗口打開的狀況
2.要考慮用戶刷新頁面的狀況
一個新的問題:
路由帶參數會有一個極端狀況,就是用戶在這個導航上再點擊一下,參數就會變成id= 'undefined'
(注: 路由參數會轉變成字符串型)
解決方法:
if (_routerParam['id'] === 'undefined' || _routerParam['id'] === undefined) {
//從新獲取id相關數據
}
複製代碼
方法: canvas.toDataURL('image/jepg',1),這種jepg格式能夠設置圖片質量,將質量設置爲1,能夠變清晰。(雖然能夠選擇以jpg or png格式導出,但實際上都是jpeg格式,目前還沒找到更好的辦法( ╯□╰ ))前端
引用canvas2image.js,可在github上找到。
function getDataURL(canvas, type, width, height) {
canvas = scaleCanvas(canvas, width, height);
//return canvas.toDataURL(type,1);原
return canvas.toDataURL('image/jpeg', 1);//改
}
複製代碼
這一塊處理起來比較麻煩,要保證刷新,新窗口打開都不出問題;另外一方面,還要考慮摺疊以後、摺疊又展開的狀態。vue
思路: 將展開的submenu`s key存在sessionStorage;下次進入再取出;另外注意點擊submenu標題時,作去重處理;另外注意摺疊以後,子菜單的css。java
selectedKeys: [], //表示當前選中menu-item opendKeys: [], //表示當前展開的submenureact
給這些須要溢出省略的,賦一個class,須要的帶上這個class,並將相同的文字內容,賦給title屬性
如:
.sampleNameCon{
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
}
複製代碼
在這個項目中,有許多相似這樣的控制按鈕。webpack
封裝一下:
import moment from 'moment';
const culateTimeRange = function (val, unit) { //數值(1),單位(day)
let _timeRange = {}
_timeRange['start'] = moment().subtract(unit, val).format('YYYY-MM-DD HH:mm:ss');
_timeRange['end'] = moment().format('YYYY-MM-DD HH:mm:ss');
return _timeRange;
}
export default culateTimeRange;
複製代碼
將後端返回的數組導出爲表(csv),用到一個庫叫:saveAsios
var file = new Blob(['\uFEFF' + res.data]); //\uFFEF是爲了不發生導出文件亂碼現象
saveAs(file, `name.csv`);
複製代碼
由於本項目會有多處的折線圖表展現,因此考慮將該部分抽象成一個組件。 用到這個庫—— import ReactEcharts from "echarts-for-react"; 有幾個點:git
習慣在vue中用watch,剛從vue遷移到react很不適應。github
能夠用react的static getDerivedStateFromProps(nextProps,nextState) 和 componentDidUpdate(prevProps,prevState)配合,實現watch的功能。
import Loadable from 'react-loadable';
<!--加載中效果-->
const PageLoading = ({ isLoading, error }) => {
if (isLoading) {
return <Spin
className="pageLoading"
size="large"
spinning={true}
/>
} else if (error) {
return <div className="pageLoadingError">資源加載失敗!</div>
} else {
return null
}
}
<!--封裝一下-->
const loadComponent = (loader,loading = PageLoading) =>{
return Loadable({
loader,
loading
})
}
// 路由
const home = loadComponent(() => import('views/show/home'))
export default{
home
}
複製代碼
咱們用webpack已經將代碼壓縮過了,可是若是開啓gzip壓縮,能夠再壓縮一半。
開啓gzip須要先後端一塊兒配合。
前端RequeshHeaders開啓—— Accept-Encoding: gzip, deflate
若是後端開啓的gzip,能夠在Response-Headers中——Content-Encoding: gzip
能夠用tinyPNG對圖片進行壓縮,固然按照業務場景能夠進行按需加載和雪碧圖
不考慮兼容性的話,推薦用谷歌新出的webp格式的圖片,小而美
第一次用react進行項目開發,學習時間很短,甚至react官網文檔我都沒讀完。從框架搭建到一些詳細的業務部分,這只是大概,我會抽空寫個更詳細的業務版。