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