資料介紹
軟件簡(jiǎn)介
項(xiàng)目開(kāi)始 BEGIN
項(xiàng)目說(shuō)明
基于 React16.x、Ant Design4.x,react-admin
目錄結(jié)構(gòu)
├── config // 項(xiàng)目構(gòu)建配置 ├── public // 不參與構(gòu)建的靜態(tài)文件 ├── scripts // 構(gòu)建腳本 ├── src │ ├── assets // 全項(xiàng)目通用圖片文件等 │ ├── commons // 全項(xiàng)目通用js,業(yè)務(wù)相關(guān) │ ├── components // 全項(xiàng)目通用組件,業(yè)務(wù)相關(guān) │ ├── config // 項(xiàng)目構(gòu)建補(bǔ)充配置 │ ├── layouts // 頁(yè)面框架布局組件+ │ ├── mock // 模擬數(shù)據(jù) │ ├── models // 模塊封裝,基于redux,提供各組件共享數(shù)據(jù)、共享邏輯 │ ├── pages // 主項(xiàng)目頁(yè)面目錄 │ ├── ├── Project // 子項(xiàng)目頂級(jí)目錄 │ ├── ├── ├── nft // NFT--PC版本項(xiàng)目目錄 │ ├── ├── ├── ├── assets // NFT--PC版本公共圖片文件 │ ├── ├── ├── ├── components // NFT--PC版本公共組件庫(kù) │ ├── ├── ├── ├── pages // NFT--PC版本頁(yè)面目錄 │ ├── ├── ├── mobile // 移動(dòng)端項(xiàng)目目錄 │ ├── ├── ├── ├── nft-mobile // NFT--微信版本項(xiàng)目目錄 │ ├── ├── ├── ├── ├── assets // NFT--微信版本公共圖片文件 │ ├── ├── ├── ├── ├── components // NFT--微信版本公共組件庫(kù) │ ├── ├── ├── ├── ├── pages // NFT--微信版本頁(yè)面目錄 │ ├── router // 路由 │ ├── ant.less // 主體配置 │ ├── App.js // 根組件 │ ├── index.css // 全局樣式 慎用 │ ├── index.dark.css // 全局樣式 慎用 │ ├── index.js // 項(xiàng)目入口 │ ├── menus.js // 菜單配置 │ ├── setupProxy.js // 后端聯(lián)調(diào)代理配置 │ └── theme.less // 主題變量 ├── package.json ├── README.md └── yarn.lock
安裝依賴(lài)
$ yarn
啟動(dòng)注意事項(xiàng)
依賴(lài)安裝完成后,src/setupProxy.js 為項(xiàng)目本地代理文件,請(qǐng)及時(shí)更改您需要代理的接口地址!
!!!微信版本只可以在微信打開(kāi)運(yùn)行,并直接在 router/AuthRoute.jsx 中填入自己申請(qǐng)的 appid,目前為''
此為兩個(gè)項(xiàng)目依托同一底層框架,PC 及微信端口分離,如需切換請(qǐng)?jiān)?router/app.router.js 中更換路由,router/AppRoute.jsx 中切換引入的 AuthRoute(分別為微信版本:AuthRoute.jsx,PC 版本:AuthRoutePC.jsx) 并在 src/commons/PRE_ROUTER.js 中進(jìn)行相關(guān)配置,以及 package.json 中更改 homepage 為相應(yīng)的項(xiàng)目名稱(chēng)。
兩套 PRE_ROUTER.js 中配置分別如下:
微信版本: 路由前綴----/front_nft_mobile 請(qǐng)求地址前綴----/api/nft 登錄頁(yè)面----/nft_mobile_home
PC 版本: 路由前綴----/front_nft_pc 請(qǐng)求地址前綴----/api/nft 登錄頁(yè)面----/nft_home
開(kāi)發(fā)啟動(dòng)
$ yarn start #指定端口 $ PORT=8080 yarn start # HTTPS方式啟動(dòng) $ HTTPS=true yarn start
生產(chǎn)構(gòu)建
$ yarn build // 構(gòu)建輸入到指定目錄 $ BUILD_PATH=../dist yarn build
域名子目錄發(fā)布項(xiàng)目
// 開(kāi)發(fā)啟動(dòng) $ BASE_NAME=/synext-admin yarn start // 開(kāi)發(fā)訪(fǎng)問(wèn) 'http://localhost:XXXX/synext-admin/' //生產(chǎn)環(huán)境 同上 $ BASE_NAME=/synext-admin yarn start // 訪(fǎng)問(wèn) 'http://xxx.com/synext-admin'
菜單配置
//在/src/menus.js文件中配置菜單數(shù)據(jù),前端硬編碼或異步加載菜單數(shù)據(jù)。 // 菜單支持頭部、左側(cè)、頭部+左側(cè)三種布局方式,默認(rèn)左側(cè)菜單。如需放開(kāi)設(shè)置,請(qǐng)到'src/layouts/index.jsx'放開(kāi)注釋 //菜單字段說(shuō)明。 字段 必須 說(shuō)明 key 是 //需要唯一 parentKey 否 //用于關(guān)聯(lián)父級(jí) path 是 //菜單對(duì)應(yīng)的路由地址 text 是 //菜單標(biāo)題 icon 否 //菜單圖標(biāo)配置 url 否 //菜單對(duì)應(yīng)會(huì)打開(kāi)url對(duì)應(yīng)的iframe頁(yè)面,如果配置了url,path將無(wú)效 target 否 //配合url使用,菜單將為a標(biāo)簽 {text} order 否 //菜單排序,數(shù)值越大越靠前顯示 type 否 //如果菜單數(shù)據(jù)中攜帶功能權(quán)限配置,type= 為菜單,type=為功能 code 否 //功能碼,如果是type=,會(huì)用到此字段
頁(yè)面開(kāi)發(fā)
//配置組件 import React, {Component} from 'react'; import config from 'src/commons/config-hoc'; @config({ title: '頁(yè)面title', ajax: true, ... }) export default class SomePage extend Component { componentDidMount() { this.props.ajax .get(...) .then(...) } ... }
參數(shù) | 類(lèi)型 | 默認(rèn)值 | 說(shuō)明 |
---|---|---|---|
noFrame | boolean | false | 標(biāo)記當(dāng)前頁(yè)面為不需要導(dǎo)航框架的頁(yè)面,比如登錄頁(yè),通過(guò)腳本抓取實(shí)現(xiàn) |
noAuth | boolean | false | 標(biāo)記當(dāng)前頁(yè)面為不需要登錄即可訪(fǎng)問(wèn)的頁(yè)面,通過(guò)腳本抓取實(shí)現(xiàn) |
keepAlive | boolean | - | 標(biāo)記當(dāng)前頁(yè)面內(nèi)容在頁(yè)面切換之后是否保持 |
title | boolean 或 string 或 ReactNode 或 object 或 function(props) | true | true:當(dāng)前頁(yè)面顯示通過(guò)菜單結(jié)構(gòu)自動(dòng)生成的 title;false:當(dāng)前頁(yè)面不顯示 title;string:自定義 title;object:{text,icon} text 為顯示的名稱(chēng),icon 為圖標(biāo);function(props): 返回值作為 title |
breadcrumbs | boolean 或 array 或 function(props) | true | true:當(dāng)前頁(yè)面顯示通過(guò)菜單結(jié)構(gòu)自動(dòng)生成的面包屑;false:當(dāng)前頁(yè)面不顯示面包屑;object:[{icon, text, ...}];function(props): 返回值作為面包屑 |
appendBreadcrumbs | array 或 function(props) | [] | 在當(dāng)前面包屑基礎(chǔ)上添加;function(props): 返回值作為新添加的面包屑 |
pageHead | boolean | - | 頁(yè)面頭部是否顯示 |
side | boolean | - | 頁(yè)面左側(cè)是否顯示 |
sideCollapsed | boolean | - | 左側(cè)是否收起 |
ajax | boolean | true | 是否添加 ajax 高階組件,內(nèi)部可以通過(guò) this.props.ajax 使用 ajax API,組件卸載時(shí),會(huì)自動(dòng)打斷未完成的請(qǐng)求 |
router | boolean | false | 是否添加 withRouter 裝飾器,組件內(nèi)部可以使用 this.props.history 等 API |
query | boolean | false | 是否添加地址查詢(xún)字符串轉(zhuǎn)換高階組件,內(nèi)部可以通過(guò) this.props.query 訪(fǎng)問(wèn)查詢(xún)字符串 |
connect | boolean 或 function(state) | false | 是否與 redux 進(jìn)行連接,true:只注入了 this.props.action 相關(guān)方法;false:不與 redux 進(jìn)行連接;(state) => ({title: state.page.title}):將函數(shù)返回的數(shù)據(jù)注入 this.props |
event | boolean | false | 是否添加 event 高階組件,可以使用 this.props.addEventListener 添加 dom 事件,并在組件卸載時(shí)會(huì)自動(dòng)清理;通過(guò) this.props.removeEventListener 移出 dom 事件 |
pubSub | boolean | false | 是否添加發(fā)布訂閱高階組件,可以使用 this.props.subscribe(topic, (msg, data) => {...})訂閱事件,并在組件卸載時(shí),會(huì)自動(dòng)取消訂閱; 通過(guò) this.props.publish(topic, data)發(fā)布事件 |
modal | string 或 object | false | 當(dāng)前組件是否是 modal。string: 彈框標(biāo)題;object:彈框配置 |
注:
-
noFrame
、noAuth
、keepAlive
?只有配置了path
才有效! -
config 裝飾器可以用于任何組件,但是
title
、breadcrumbs
、appendBreadcrumbs
、pageHead
、side
、sideCollapsed
最好在路由對(duì)應(yīng)的頁(yè)面組件中使用
//頁(yè)面保持 //頁(yè)面渲染一次之后會(huì)保持狀態(tài),再次跳轉(zhuǎn)到此頁(yè)面不會(huì)重新創(chuàng)建或重新渲染 開(kāi)啟方式: 1. /src/models/system.js initState.keepAlive 屬性修改默認(rèn)值 2. config裝飾器 keepAlive屬性
頁(yè)面顯示/隱藏事件
config
?裝飾器為組件注入了兩個(gè)事件?onComponentWillShow
、onComponentWillHide
?,如果頁(yè)面使用了 Keep Alive 功能,切換顯示/隱藏時(shí)會(huì)觸發(fā)
@config({ ... }) export default class SomePage extends React.Component { constructor(...props) { super(...props); this.props.onComponentWillShow(() => { // do some thing }); this.props.onComponentWillHide(() => { // do some thing }); } ... }
頁(yè)面容器 PageContent
系統(tǒng)提供了頁(yè)面的跟節(jié)點(diǎn) PageContent,有如下特性:
- 添加了 margin padding 樣式;
- 添加了 footer;
- 支持頁(yè)面 loading;
- 自動(dòng)判定是否有底部工具條 FixBottom 組件,為底部工具條騰出空間;
是否顯示 footer,默認(rèn) true
<PageContent footer={false}>...</PageContent>
顯示 loading,有兩種方式。
-
model 方式
this.props.action.page.showLoading(); this.props.action.page.hideLoading();
? -
props 方式
const { loading } = this.state; <PageContent loading={loading}>...</PageContent>;
彈框頁(yè)面開(kāi)發(fā)
添加、修改等場(chǎng)景,往往會(huì)用到彈框,antd Modal 組件使用不當(dāng)會(huì)產(chǎn)生臟數(shù)據(jù)問(wèn)題(兩次彈框渲染數(shù)據(jù)互相干擾)
系統(tǒng)提供了基于 modal 封裝的高階組件,每次彈框關(guān)閉,都會(huì)銷(xiāo)毀彈框內(nèi)容,避免互相干擾
modal 高階組件
modal 高階組件集成到了 config 中,也可以單獨(dú)引用:import { ModalContent } from 'src/commons/ra-lib';
import React from "react"; import config from "src/commons/config-hoc"; import { ModalContent } from "src/commons/ra-lib"; export default config({ modal: { title: "彈框標(biāo)題", }, })((props) => { const { onOk, onCancel } = props; return ( <ModalContent onOk={onOk} onCancel={onCancel}> 彈框內(nèi)容 ModalContent> ); });
modal 所有參數(shù)說(shuō)明如下:
- 如果是 string,作為 modal 的 title
- 如果是函數(shù),函數(shù)返回值作為 Modal 參數(shù)
- 如果是對(duì)象,為 Modal 相關(guān)配置,具體參考?antd Modal 組件
- options.fullScreen boolean 默認(rèn) false,是否全屏顯示彈框
ModalContent 組件
彈框內(nèi)容通過(guò) ModalContent 包裹,具體參數(shù)如下:
參數(shù) | 類(lèi)型 | 默認(rèn)值 | 說(shuō)明 |
---|---|---|---|
surplusSpace | boolean | false | 是否使用屏幕垂直方向剩余空間 |
otherHeight | number | - | 除了主體內(nèi)容之外的其他高度,用于計(jì)算主體高度; |
loading | boolean | false | 加載中 |
loadingTip | - | - | 加載提示文案 |
footer | - | - | 底部 |
okText | string | - | 確定按鈕文案 |
onOk | function | - | 確定按鈕事件 |
cancelText | string | - | 取消按鈕文案 |
onCancel | function | - | 取消按鈕事件 |
resetText | string | - | 重置按鈕文案 |
onReset | function | - | 重置按鈕事件 |
style | object | - | 最外層容器樣式 |
bodyStyle | object | - | 內(nèi)容容器樣式 |
系統(tǒng)路由
系統(tǒng)路由使用 react-router,通過(guò) route-loader 將路由內(nèi)容填充到/src/pages/page-routes.js 文件,支持兩種寫(xiě)法:
-
常量方式
export const PAGE_ROUTE = "/path";
? -
頁(yè)面 config 裝飾器(推薦)
@config({ path: '/path', }) export default class SomePage extends React.Component { ... }
二級(jí)頁(yè)面
二級(jí)頁(yè)面如果要保持父級(jí)菜單的選中狀態(tài),以父級(jí) path 開(kāi)始并以/_/
作為分隔符即可:parent/path/_/child/path
// parent page @config({ path: '/parent/path' }) export default class Parent extends React.Component { ... } // child page @config({ path: '/parent/path/_/child/path' }) export default class Parent extends React.Component { ... }
AJAX 請(qǐng)求
系統(tǒng)的 ajax 請(qǐng)求基于 axios 封裝。 基于 restful 規(guī)范,提供了 5 個(gè)方法:
- get 獲取服務(wù)端數(shù)據(jù),參數(shù)拼接在 url 上,以 query string 方式發(fā)送給后端
- post 新增數(shù)據(jù),參數(shù)以 body 形式發(fā)送給后端
- put 修改數(shù)據(jù),參數(shù)以 body 形式發(fā)送給后端
- del 刪除數(shù)據(jù),參數(shù)拼接在 url 上,以 query string 方式發(fā)送給后端
- patch 修改部分?jǐn)?shù)據(jù),參數(shù)以 body 形式發(fā)送給后端
調(diào)用方式 三種
//第一種 config裝飾器ajax屬性(推薦) import React, {Component} from 'react'; import config from 'src/commons/config-hoc'; @config({ ajax: true, ... }) export default class SomePage extend Component { componentDidMount() { this.props.ajax .get(...) .then(...) } ... } //第二種 ajax裝飾器 import React, {Component} from 'react'; import {ajaxHoc} from 'src/commpons/ajax'; @ajaxHoc() export default class SomePage extend Component { componentDidMount() { this.props.ajax .get(...) .then(...) } ... } //第三種 直接引入ajax對(duì)象 import React, {Component} from 'react'; import {sxAjax} from 'src/commpons/ajax'; export default class SomePage extend Component { componentDidMount() { sxAjax.post(...).then(...); // 組件卸載或者其他什么情況,需要打算ajax請(qǐng)求,可以用如下方式 const ajax = sxAjax.get(...); ajax.then(...).finally(...); ajax.cancel(); } ... }
注:config、ajaxHoc 方式做了封裝,頁(yè)面被卸載之后會(huì)自動(dòng)打斷未完成的請(qǐng)求
接收參數(shù)
所有的 ajax 方法參數(shù)統(tǒng)一,都能夠接受三個(gè)參數(shù): 參數(shù)|說(shuō)明 ---|--- url|請(qǐng)求地址 params|請(qǐng)求傳遞給后端的參數(shù) options|請(qǐng)求配置,即 axios 的配置。
options 配置
參數(shù) | 說(shuō)明 |
---|---|
axios 配置 | 可以接受 axios 參數(shù) |
successTip | 擴(kuò)展的參數(shù),成功提示 |
errorTip | 擴(kuò)展的參數(shù),失敗提示 |
noEmpty | 擴(kuò)展的參數(shù),過(guò)濾掉 ''、null、undefined 的參數(shù),不提交給后端 |
originResponse | 擴(kuò)展參數(shù),.then 中可以拿到完整的 response,而不只是 response.data |
注:全局默認(rèn)參數(shù)可以在 src/commons/ajax.js 中進(jìn)行配置,默認(rèn) 、timeout=1000 * 60。
請(qǐng)求結(jié)果提示
- 系統(tǒng)對(duì) ajax 失敗做了自動(dòng)提示,開(kāi)發(fā)人員可通過(guò) src/commons/handle-error.js 進(jìn)行配置;
- 成功提示默認(rèn)不顯示,如果需要成功提示,可以配置 successTip 參數(shù),或者.then()中自行處理;
- 成功提示在 src/commons/handle-success.js 中配置;
this.props.ajax.del("/user/1", null, { successTip: "刪除成功!", errorTip: "刪除失敗!", noEmpty: true, });
loading 處理
系統(tǒng)擴(kuò)展了 promise,提供了 finally 方法,用于無(wú)論成功還是失敗,都要進(jìn)行的處理。一般用于關(guān)閉 loading
this.setState({loading: true}); this.props.ajax .get('/url') .then(...) .finally(() => this.setState({loading: false}));
Mock 模擬數(shù)據(jù)
前后端并行開(kāi)發(fā),為了方便后端快速開(kāi)發(fā),不需要等待后端接口,系統(tǒng)提供了 mock 功能。基于mockjs
編寫(xiě)模擬數(shù)據(jù)
在/src/mock 目錄下進(jìn)行 mock 數(shù)據(jù)編寫(xiě),比如:
import { getUsersByPageSize } from "./mockdata/user"; export default { "post /mock/login": (config) => { const { userName, password } = JSON.parse(config.data); return new Promise((resolve, reject) => { if (userName !== "test" || password !== "111") { setTimeout(() => { reject({ code: 1001, message: "用戶(hù)名或密碼錯(cuò)誤", }); }, 1000); } else { setTimeout(() => { resolve([ 200, { id: "1234567890abcde", name: "MOCK 用戶(hù)", loginName: "MOCK 登錄名", }, ]); }, 1000); } }); }, "post /mock/logout": {}, "get /mock/user-center": (config) => { const { pageSize, pageNum } = config.params; return new Promise((resolve) => { setTimeout(() => { resolve([ 200, { pageNum, pageSize, total: 888, list: getUsersByPageSize(pageSize), }, ]); }, 1000); }); }, "get re:/mock/user-center/.+": { id: 1, name: "熊大", age: 22, job: "前端", }, "post /mock/user-center": true, "put /mock/user-center": true, "delete re:/mock/user-center/.+": "id", };
簡(jiǎn)化
為了方便 mock 接口編寫(xiě),系統(tǒng)提供了簡(jiǎn)化腳本(/src/mock/simplify.js),上面的例子就是簡(jiǎn)化寫(xiě)法
對(duì)象的 key 由 method url delay,各部分組成,以空格隔開(kāi)
字段 | 說(shuō)明 |
---|---|
method | 請(qǐng)求方法 get post 等 |
url | 請(qǐng)求的 url |
delay | 模擬延遲,毫秒 默認(rèn) 1000 |
調(diào)用
系統(tǒng)封裝的 ajax 可以通過(guò)以下兩種方式,自動(dòng)區(qū)分是 mock 數(shù)據(jù),還是真實(shí)后端數(shù)據(jù),無(wú)需其他配置
mock 請(qǐng)求:
- url 以/mock/開(kāi)頭的請(qǐng)求
- /src/mock/url-config.js 中配置的請(qǐng)求
this.props.ajax.get('/mock/users').then(...);
如果后端真實(shí)接口準(zhǔn)備好之后,去掉 url 中的/mock 即可
注:mock 功能只有開(kāi)發(fā)模式下開(kāi)啟了,生產(chǎn)模式不會(huì)開(kāi)啟 mock 功能,如果其他環(huán)境要開(kāi)啟 mock 使用 MOCK=true 參數(shù),比如?MOCK=true yarn build
樣式
系統(tǒng)使用less進(jìn)行樣式的編寫(xiě)。 為了避免多人合作樣式?jīng)_突,系統(tǒng)對(duì) src 下的 less 文件啟用了 Css Module,css 文件沒(méi)有使用 Css Module。
style.less
.root { width: 100%; height: 100%; }
Some.jsx
import "/path/to/style.less"; export default class Some extends React.Component { render() { return <div styleName="root">div>; } }
注:基礎(chǔ)組件不使用 Css Module,不利于樣式覆蓋;
主題
使用 less,通過(guò)樣式覆蓋來(lái)實(shí)現(xiàn)。
編寫(xiě)主題
- less 文件中使用主題相關(guān)變量;
-
編寫(xiě)
/src/theme.less
通過(guò)less-loader的modifyVars
覆蓋 less 中的變量;
注:目前每次修改了 theme.less 需要重新 yarn start 才能生效
參考
- Ant Design 主題 參考:https://ant-design.gitee.io/docs/react/customize-theme-cn
導(dǎo)航布局
為了滿(mǎn)足不同系統(tǒng)的需求,提供了四種導(dǎo)航布局:
- 頭部菜單
- 左側(cè)菜單
- 頭部+左側(cè)菜單
- tab 頁(yè)方式
更改方式
- 用戶(hù)可以通過(guò) 頁(yè)面有上角用戶(hù)頭像 -> 設(shè)置 頁(yè)面進(jìn)行選擇(如果您為用戶(hù)提供了此頁(yè)面);
-
開(kāi)發(fā)人員可以通過(guò)修改
src/models/index.js
指定布局方式;
不需要導(dǎo)航
有些頁(yè)面可能不需要顯示導(dǎo)航,可以通過(guò)如下任意一種方式進(jìn)行設(shè)置:
-
頁(yè)面配置高級(jí)組件
@config({ noFrame: true, })
? -
瀏覽器 url 中 noFrame=true 參數(shù)
/path/to?noFrame=true
Tab 標(biāo)簽頁(yè)
頁(yè)面頭部標(biāo)簽,有如下特性:
- 在當(dāng)前 tab 標(biāo)簽之后打開(kāi)新的 tab 標(biāo)簽;
- 記錄并恢復(fù)滾動(dòng)條位置;
-
保持頁(yè)面狀態(tài)(需要開(kāi)啟
Keep Page Alive
); - tab 標(biāo)簽右鍵操作;
- tab 頁(yè)操作 API;
- tab 標(biāo)簽拖拽排序;
- 關(guān)閉一個(gè)二級(jí)頁(yè)面 tab,嘗試打開(kāi)它的父級(jí);
Tab 操作 API
system model(redux)中提供了如下操作 tab 頁(yè)的方法:
API | 說(shuō)明 |
---|---|
setCurrentTabTitle(title) | 設(shè)置當(dāng)前激活的 tab 標(biāo)題 title: stirng 或 {text, icon} |
refreshTab(targetPath) | 刷新 targetPath 指定的 tab 頁(yè)內(nèi)容(重新渲染) |
refreshAllTab() | 刷新所有 tab 頁(yè)內(nèi)容(重新渲染) |
closeCurrentTab() | 關(guān)閉當(dāng)前 tab 頁(yè) |
closeTab(targetPath) | 關(guān)閉 targetPath 對(duì)應(yīng)的 tab 頁(yè) |
closeOtherTabs(targetPath) | 關(guān)閉除了 targetPath 對(duì)應(yīng)的 tab 頁(yè)之外的所有 tab 頁(yè) |
closeAllTabs() | 關(guān)閉所有 tab 頁(yè),系統(tǒng)將跳轉(zhuǎn)首頁(yè) |
closeLeftTabs(targetPath) | 關(guān)閉 targetPath 對(duì)應(yīng)的 tab 頁(yè)左側(cè)所有 tab 頁(yè) |
closeRightTabs(targetPath) | 關(guān)閉 targetPath 對(duì)應(yīng)的 tab 頁(yè)右側(cè)所有的 tab 頁(yè) |
使用方式:
import config from 'src/commons/config-hoc'; @config({ connect: true, }) export default class SomeComponent extends React.Component { componentDidMount() { this.props.action.system.closeTab('/some/path'); } ... }
注:
-
tab 基于頁(yè)面地址,每當(dāng)使用
this.props.history.push('/some/path')
,就會(huì)選中或者新打開(kāi)一個(gè) tab 頁(yè)(/path
?與?/path?name=Tom
屬于不同 url 地址,會(huì)對(duì)應(yīng)兩個(gè) tab 頁(yè)); - 沒(méi)有菜單對(duì)應(yīng)的頁(yè)面,需要單獨(dú)設(shè)置 title,否則 tab 標(biāo)簽將沒(méi)有 title;
models(redux) 封裝
基于redux進(jìn)行封裝,不改變 redux 源碼,可以結(jié)合使用 redux 社區(qū)中其他解決方案。
注:一般情況下,用不到 redux~
models 用于管理數(shù)據(jù),解決的問(wèn)題:
- 命名空間(防止數(shù)據(jù)、方法命名沖突):數(shù)據(jù)與方法,都?xì)w屬于具體 model,比如:state.userCenter.xxx,this.props.action.userCenter.xxx();
- 如何方便的獲取數(shù)據(jù):connect 與組件連接;@connect(state => ({name: state.user.name}));
- 如何方便的修改數(shù)據(jù):this.props.action 中方法;
- 客戶(hù)端數(shù)據(jù)持久化(保存到 LocalStorage 中):syncStorage 配置;
- 異步數(shù)據(jù)處理:基于 promise 異步封裝;
- 請(qǐng)求錯(cuò)誤提示:error 處理封裝,errorTip 配置,自動(dòng)提示;
- 請(qǐng)求成功提示:successTip 配置,自動(dòng)提示;
-
簡(jiǎn)化寫(xiě)法:types actions reducers 可以在一個(gè)文件中編寫(xiě),較少?zèng)_突,方便多人協(xié)作,參見(jiàn)
models/page.js
中的寫(xiě)法; - 業(yè)務(wù)代碼可集中歸類(lèi):在 models 目錄中統(tǒng)一編寫(xiě),或者在具體業(yè)務(wù)目錄中,模塊化方式。
src/models
所有的 model 直接在 models 或 pages 下定義:
model 模塊名規(guī)則:
/path/to/models/user-center.js --> userCenter; /path/to/models/user.js --> user; /path/to/pages/users/model.js --> users; /path/to/pages/users/job.model.js --> job; /path/to/pages/users/user-center.model.js --> userCenter; /path/to/pages/users/user.center.model.js --> userCenter;
組件與 redux 進(jìn)行連接
提供了多種種方式,裝飾器方式、函數(shù)調(diào)用、hooks、js 文件直接使用;
裝飾器
推薦使用裝飾器方式
import {connect} from 'path/to/models'; @connect(state => { return { ... } }) class Demo extends Component{ ... }
函數(shù)
import {connectComponent} from 'path/to/models'; class Demo extends Component { ... } function mapStateToProps(state) { return { ... }; } export default connectComponent({LayoutComponent: Demo, mapStateToProps});
hooks
import { useSelector } from "react-redux"; import { useAction } from "src/models"; export default () => { const action = useAction(); const show = useSelector((state) => state.side.show); console.log(show); useEffect(() => { action.side.hide(); }, []); return <div />; };
對(duì) useSelector 的說(shuō)明:
useSelector(select) 默認(rèn)對(duì) select 函數(shù)的返回值進(jìn)行引用比較 ===,并且僅在返回值改變時(shí)觸發(fā)重渲染。
即:如果 select 函數(shù)返回一個(gè)臨時(shí)對(duì)象,會(huì)多次 re-render
最好不要這樣使用: const someData = useSelector(state => { // 每次都返回一個(gè)新對(duì)象,導(dǎo)致re-render return {name: state.name, age: state.age}; }) 最好多次調(diào)用useSelector,單獨(dú)返回?cái)?shù)據(jù),或者返回非引用類(lèi)型數(shù)據(jù) const name = useSelector(state => state.firstName + state.lastName); const age = useSelector(state => state.age);
js 文件中使用
沒(méi)有特殊需求,一般不會(huì)在普通 js 文件中使用
import { action, store } from "src/models"; // 獲取數(shù)據(jù) const state = store.getState(); // 修改數(shù)據(jù) action.side.hide();
簡(jiǎn)化寫(xiě)法
action reducer 二合一,省去了 actionType,簡(jiǎn)化寫(xiě)法;
注意:
- 所有的 reducer 方法,無(wú)論是什么寫(xiě)法中的,都可以直接返回新數(shù)據(jù),不必關(guān)心與原數(shù)據(jù)合并(...state),封裝內(nèi)部做了合并;
- 一個(gè) model 中,除了 initialState syncStorage actions reducers 等關(guān)鍵字之外的屬性,都視為 action reducer 合并寫(xiě)法;
一個(gè)函數(shù)
一個(gè)函數(shù),即可作為 action 方法,也作為 reduce 使用
- 調(diào)用 action 方法傳遞的數(shù)據(jù)將不會(huì)做任何處理,會(huì)直接傳遞給 reducer
- 只能用第一個(gè)參數(shù)接收傳遞過(guò)來(lái)的數(shù)據(jù),如果多個(gè)數(shù)據(jù),需要通過(guò)對(duì)象方式傳遞,如果不需要傳遞數(shù)據(jù),但是要使用 state,也需要定義一個(gè)參數(shù)占位
- 第二個(gè)參數(shù)固定為 state,第三個(gè)參數(shù)固定為 action,不需要可以缺省(一般都是缺省的)
- 函數(shù)的返回值為一個(gè)對(duì)象或者 undefined,將于原 state 合并,作為 store 新的 state
// page.model.js export default { initialState: { title: void 0, name: void 0, user: {}, toggle: true, }, setTitle: (title) => ({ title }), setName: (name, state, action) => { const { name: prevName } = state; if (name !== prevName) return { name: "Different Name" }; }, setUser: ({ name, age } = {}) => ({ user: { name, age } }), setToggle: (arg, state) => ({ toggle: !state.toggle }), }; // 使用 this.props.action.page.setTitle("my title");
數(shù)據(jù)同步
通過(guò)配置的方式,可以讓 redux 中的數(shù)據(jù)自動(dòng)與 localStorage 同步
export default { initialState: { title: '', show: true, user: {}, users: [], job: {}, total: 0, loading: false, ... }, // initialState會(huì)全部同步到localStorage中 // syncStorage: true, // 配置部分存數(shù)據(jù)儲(chǔ)到localStorage中 syncStorage: { titel: true, user: { // 支持對(duì)象指定字段,任意層次 name: true, address: { city: true, }, }, job: true, users: [{name: true, age: true}], // 支持?jǐn)?shù)組 }, }
action reducer 合并寫(xiě)法
如果 action 有額外的數(shù)據(jù)處理,并且一個(gè) action 只對(duì)應(yīng)一個(gè) reducer,這種寫(xiě)法不需要指定 actionType,可以有效簡(jiǎn)化代碼;
export default { initialState: { title: '', ... }, arDemo: { // 如果是函數(shù)返回值將作為action.payload 傳遞給reducer,如果非函數(shù),直接將payload的值,作為action.payload; payload(options) {...}, // 如果是函數(shù)返回值將作為action.meta 傳遞給reducer,如果非函數(shù),直接將meta的值,作為action.meta; meta(options) {...}, reducer(state, action) { returtn {...newState}; // 可以直接返回要修改的數(shù)據(jù),內(nèi)部封裝會(huì)與原state合并`{...state, ...newState}`; }, }, };
異步 action 寫(xiě)法
export default { initialState: { title: '', ... }, fetchUser: { // 異步action payload 返回promise payload: ({params, options}) => axios.get('/mock/users', params, options), // 異步action 默認(rèn)使用通用異步meta配置 commonAsyncMeta,對(duì)successTip errorTip onResolve onReject onComplete 進(jìn)行了合理的默認(rèn)值處理,需要action以對(duì)象形式傳參調(diào)用 // meta: commonAsyncMeta, // meta: { // successTip: '查詢(xún)成功!歐耶~', // errorTip: '自定義errorTip!馬丹~', // }, // meta: () => { // return {...}; // }, // 基于promise 異步reducer寫(xiě)法; reducer: { pending: (state, action) => ({loading: true}), resolve(state, {payload = {}}) { const {total = 0, list = []} = payload; return { users: list, total, } }, complete: (state, action) => ({loading: false}), } }, };
調(diào)用方式:
this.props.action.user.fetchUser({ params, options, successTip, errorTip, onResolve, onReject, onComplete, });
參數(shù)約定為一個(gè)對(duì)象,各個(gè)屬性說(shuō)明如下:
參數(shù) | 說(shuō)明 |
---|---|
params | 請(qǐng)求參數(shù) |
options | 請(qǐng)求配置 |
successTip | 成功提示信息 |
errorTip | 錯(cuò)誤提示信息 |
onResolve | 成功回調(diào) |
onReject | 失敗回調(diào) |
onComplete | 完成回調(diào),無(wú)論成功、失敗都會(huì)調(diào)用 |
單獨(dú)定義 action 和 reducer
支持這種比較傳統(tǒng)的寫(xiě)法,一般也不會(huì)太用到
import {createAction} from 'redux-actions'; export const types = { GET_MENU_STATUS: 'MENU:GET_MENU_STATUS', // 防止各個(gè)模塊沖突,最好模塊名開(kāi)頭 }; export default { initialState: { title: '', ... }, // 單獨(dú)action定義,需要使用actionType與reducer進(jìn)行關(guān)聯(lián) actions: { getMenuStatus: createAction(types.GET_MENU_STATUS), }, // 單獨(dú)reducer定義,使用了actionType,不僅可以處理當(dāng)前model中的action // 也可以處理其他任意action(只要actionType能對(duì)應(yīng)) reducers: { [types.GET_MENU_STATUS](state) { ... return { ... }; } }, }
權(quán)限控制
系統(tǒng)菜單、具體功能點(diǎn)都可以進(jìn)行權(quán)限控制。
菜單權(quán)限
菜單由后端提供(一般系統(tǒng)都是后端提供),后臺(tái)通過(guò)登錄用戶(hù)返回用戶(hù)的菜單權(quán)限;頁(yè)面只顯示獲取到的菜單;
系統(tǒng)提供了一個(gè)基礎(chǔ)的菜單、權(quán)限管理頁(yè)面,需要后端配合存儲(chǔ)數(shù)據(jù)。
功能權(quán)限
可以通過(guò)src/components/permission
組件對(duì)功能的權(quán)限進(jìn)行控制
import React, { Component } from "react"; import Permission from "src/components/permission"; export default class SomePage extends Component { render() { return ( <div> <Permission code="USER_ADD"> <Button>添加用戶(hù)</Button> </Permission> </div> ); } }
注:權(quán)限的 code 前端使用時(shí)會(huì)硬編碼,注意語(yǔ)義化、唯一性。
角色
一般系統(tǒng)都會(huì)提供角色管理功能,系統(tǒng)中提供了一個(gè)基礎(chǔ)的角色管理功能,稍作修改即可使用。
開(kāi)發(fā)代理
開(kāi)發(fā)時(shí),要與后端進(jìn)行接口對(duì)接,可以通過(guò)代理與后端進(jìn)行連接,開(kāi)發(fā)代理配置在src/setupProxy.js
中編寫(xiě)
const proxy = require("http-proxy-middleware"); const prefix = process.env.AJAX_PREFIX || "/api"; module.exports = function (app) { app.use( proxy(prefix, { target: "http://localhost:3000/", pathRewrite: { ["^" + prefix]: "", // 如果后端接口無(wú)前綴,可以通過(guò)這種方式去掉 }, changeOrigin: true, secure: false, // 是否驗(yàn)證證書(shū) ws: true, // 啟用websocket }) ); };
注:更多代理配置請(qǐng)參考http-proxy-middleware
前端默認(rèn) ajax 前綴 /api 可以通過(guò) AJAX_PREFIX 參數(shù)進(jìn)行修改。
nginx 配置參考
這里只是參考文件,根據(jù)自己的項(xiàng)目需求自行配置
一個(gè)域名對(duì)應(yīng)單個(gè)項(xiàng)目
目錄結(jié)構(gòu)
. ├── /usr/local/nginx/html │ ├── static │ ├── index.html │?? └── favicon.ico
nginx 配置
# 后端服務(wù)地址 upstream api_service { server xxx.xxx.xxx.xxx:xxxx; keepalive 2000; } server { listen 80; server_name www.xxxx.com xxxx.com; # 域名地址 root /usr/local/nginx/html; # 前端靜態(tài)文件目錄 location / { index index.html; try_files $uri $uri/ /index.html; #react-router 防止頁(yè)面刷新出現(xiàn)404 } # 靜態(tài)文件緩存,啟用Cache-Control: max-age、Expires location ~ ^/static/(css|js|media)/ { expires 10y; access_log off; add_header Cache-Control "public"; } # 代理ajax請(qǐng)求 前端ajax請(qǐng)求以 /api 開(kāi)頭 location ^~/api { rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是統(tǒng)一以api開(kāi)頭,去掉api前綴 proxy_pass http://api_service/; proxy_set_header Host $http_host; proxy_set_header Connection close; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Server $host; } }
一個(gè)域名對(duì)應(yīng)多個(gè)項(xiàng)目
多個(gè)項(xiàng)目掛載到同一個(gè)域名下,可以通過(guò)子目錄方式區(qū)分
比如,如下地址各對(duì)應(yīng)一個(gè)項(xiàng)目
前端項(xiàng)目構(gòu)建時(shí),添加 BASE_NAME PUBLIC_URL 參數(shù)
BASE_NAME=/project1 PUBLIC_URL=/project1 yarn build
nginx 靜態(tài)文件目錄結(jié)構(gòu)
. ├── /home/ubuntu/synext-admin │?? ├── build // 主項(xiàng)目 靜態(tài)文件目錄 │ │ ├── static │ │ ├── index.html │ │?? └── favicon.ico │?? ├── project1 // 子項(xiàng)目靜態(tài)目錄 名稱(chēng)與 location /project1 location ~ ^/project1/static/.* 配置對(duì)應(yīng) │ │ ├── static │ │ ├── index.html │ │?? └── favicon.ico
nginx 配置
upstream api_service { server xxx.xxx.xxx.xxx:xxxx; keepalive 2000; } upstream api_service_project1 { server xxx.xxx.xxx.xxx:xxxx; keepalive 2000; } server { listen 80; server_name www.xxxx.com xxxx.com; # 域名地址 # Allow file uploads client_max_body_size 100M; # 主項(xiàng)目配置,訪(fǎng)問(wèn)地址 http://www.xxxx.com location / { root /home/ubuntu/synext-admin/build; index index.html; try_files $uri $uri/ /index.html; } # 靜態(tài)文件緩存,啟用Cache-Control: max-age、Expires location ~ ^/static/.* { root /home/ubuntu/synext-admin/build; expires 20y; access_log off; add_header Cache-Control "public"; } # 代理ajax請(qǐng)求 前端ajax請(qǐng)求以/api開(kāi)頭 location ^~/api { rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是統(tǒng)一以api開(kāi)頭,去掉api前綴 proxy_pass http://api_service/; proxy_set_header Host $http_host; proxy_set_header Connection close; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Server $host; } # 子項(xiàng)目配置 訪(fǎng)問(wèn)地址 http://www.xxxx.com/project1 location /project1 { root /home/ubuntu/synext-admin; index index.html; try_files $uri $uri/ /project1/index.html; } # 靜態(tài)文件緩存,啟用Cache-Control: max-age、Expires location ~ ^/project1/static/.* { root /home/ubuntu/synext-admin; expires 10y; access_log off; add_header Cache-Control "public"; } # 代理ajax請(qǐng)求 前端ajax請(qǐng)求以 /project1_api 開(kāi)頭 location ^~/project1_api { rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是統(tǒng)一以api開(kāi)頭,去掉api前綴 proxy_pass http://api_service_project1/; proxy_set_header Host $http_host; proxy_set_header Connection close; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Server $host; } }
其他
頁(yè)面打印
通過(guò)給元素添加相應(yīng)的 class,控制打印內(nèi)容:
-
.just-print
?只在打印時(shí)顯示 -
.no-print
?在打印時(shí)不顯示
ESLint 說(shuō)明
如果前端項(xiàng)目,不是 git 根目錄,在提交的時(shí)候,會(huì)報(bào)錯(cuò)?Not a git repository
修改 package.json,lint-staged 如下即可
"lint-staged": { "gitDir": "../", "linters": { "**/*.{js,jsx}": "lint-staged:js", "**/*.less": "stylelint --syntax less" } },
Webpack
使用了 alias {'@': '/path/to/src', src:'/path/to/src'}
- config/webpack.config.js
- 方便路徑書(shū)寫(xiě),不必關(guān)心相對(duì)路徑結(jié)構(gòu)
- 復(fù)制粘貼到其他文件,不必修改路徑
支持判斷運(yùn)算符
const name = res?.data?.user?.name || "匿名";
form 表單
- FormElement:類(lèi)型有:
'input', 'hidden', 'number', 'textarea', 'password', 'mobile', 'email', 'select', 'select-tree', 'checkbox', 'checkbox-group', 'radio', 'radio-button', 'radio-group', 'switch', 'date', 'time', 'date-time', 'date-range', 'cascader', 'transfer', 'icon-picker'
- 步道保護(hù)NFT開(kāi)源分享
- 工廠(chǎng)監(jiān)視器(傳感器到前端)開(kāi)源分享
- opennft開(kāi)源NFT交易平臺(tái)
- [前端方案]火焰識(shí)別技術(shù)材料
- 基于TQF6297下的B7/B30/B38/B40/B41N Front-End Module (FEM)
- 基于MCP3913下的3V 六通道模擬前端
- 基于MCP2030下的3 通道模擬前端器件
- 基于MCP3901下的兩通道模擬前端
- 基于MCP3914下的3V 八通道模擬前端
- 3.3V雙通道模擬前端之MCP3911中文手冊(cè)
- 基于MCP3919下的3V 三通道模擬前端
- 基于航盛的汽車(chē)信息終端平臺(tái)研發(fā) 9次下載
- 交叉平臺(tái)開(kāi)源編譯系統(tǒng)_cmake入門(mén) 9次下載
- Front End Turns PC Sound Card in 4次下載
- Simplifying RF front-end desig
- AFE模擬前端芯片是什么 AFE模擬前端芯片怎么用 3942次閱讀
- 模擬前端芯片的差異分析 627次閱讀
- 模擬前端AFE是什么器件 1226次閱讀
- 模擬前端電路的工作原理和作用 1056次閱讀
- 什么是模擬前端芯片技術(shù) 數(shù)字前端和模擬前端的區(qū)別 1108次閱讀
- AFE模擬前端的組成 606次閱讀
- 模擬前端電路指的是什么 698次閱讀
- 全平臺(tái)系統(tǒng)開(kāi)源免費(fèi)抓包軟件ProxyPin概述 1724次閱讀
- 騰訊開(kāi)源的前端框架介紹 1813次閱讀
- 前沿開(kāi)源技術(shù)領(lǐng)域的開(kāi)源大數(shù)據(jù)一一解讀 1005次閱讀
- 前端技術(shù)是什么 1767次閱讀
- 推薦一款基于RISC-V MCU的開(kāi)源SoC平臺(tái) 3445次閱讀
- 2018年,F(xiàn)acebook總共開(kāi)源了153個(gè)新項(xiàng)目 4393次閱讀
- 前端開(kāi)發(fā)環(huán)境介紹_前端開(kāi)發(fā)環(huán)境安裝與配置 1.7w次閱讀
- 射頻硬件平臺(tái)-indie使用AWR設(shè)計(jì) 842次閱讀
下載排行
本周
- 1山景DSP芯片AP8248A2數(shù)據(jù)手冊(cè)
- 1.06 MB | 532次下載 | 免費(fèi)
- 2RK3399完整板原理圖(支持平板,盒子VR)
- 3.28 MB | 339次下載 | 免費(fèi)
- 3TC358743XBG評(píng)估板參考手冊(cè)
- 1.36 MB | 330次下載 | 免費(fèi)
- 4DFM軟件使用教程
- 0.84 MB | 295次下載 | 免費(fèi)
- 5元宇宙深度解析—未來(lái)的未來(lái)-風(fēng)口還是泡沫
- 6.40 MB | 227次下載 | 免費(fèi)
- 6迪文DGUS開(kāi)發(fā)指南
- 31.67 MB | 194次下載 | 免費(fèi)
- 7元宇宙底層硬件系列報(bào)告
- 13.42 MB | 182次下載 | 免費(fèi)
- 8FP5207XR-G1中文應(yīng)用手冊(cè)
- 1.09 MB | 178次下載 | 免費(fèi)
本月
- 1OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費(fèi)
- 2555集成電路應(yīng)用800例(新編版)
- 0.00 MB | 33566次下載 | 免費(fèi)
- 3接口電路圖大全
- 未知 | 30323次下載 | 免費(fèi)
- 4開(kāi)關(guān)電源設(shè)計(jì)實(shí)例指南
- 未知 | 21549次下載 | 免費(fèi)
- 5電氣工程師手冊(cè)免費(fèi)下載(新編第二版pdf電子書(shū))
- 0.00 MB | 15349次下載 | 免費(fèi)
- 6數(shù)字電路基礎(chǔ)pdf(下載)
- 未知 | 13750次下載 | 免費(fèi)
- 7電子制作實(shí)例集錦 下載
- 未知 | 8113次下載 | 免費(fèi)
- 8《LED驅(qū)動(dòng)電路設(shè)計(jì)》 溫德?tīng)栔?/a>
- 0.00 MB | 6656次下載 | 免費(fèi)
總榜
- 1matlab軟件下載入口
- 未知 | 935054次下載 | 免費(fèi)
- 2protel99se軟件下載(可英文版轉(zhuǎn)中文版)
- 78.1 MB | 537798次下載 | 免費(fèi)
- 3MATLAB 7.1 下載 (含軟件介紹)
- 未知 | 420027次下載 | 免費(fèi)
- 4OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費(fèi)
- 5Altium DXP2002下載入口
- 未知 | 233046次下載 | 免費(fèi)
- 6電路仿真軟件multisim 10.0免費(fèi)下載
- 340992 | 191187次下載 | 免費(fèi)
- 7十天學(xué)會(huì)AVR單片機(jī)與C語(yǔ)言視頻教程 下載
- 158M | 183279次下載 | 免費(fèi)
- 8proe5.0野火版下載(中文版免費(fèi)下載)
- 未知 | 138040次下載 | 免費(fèi)
評(píng)論
查看更多