<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          opennft-front開源 NFT 前端平臺

          聯(lián)合創(chuàng)作 · 2023-09-18 10:39

          項(xiàng)目開始 BEGIN

          項(xiàng)目說明

          基于 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         // 頁面框架布局組件+
          │   ├── mock            // 模擬數(shù)據(jù)
          │   ├── models          // 模塊封裝,基于redux,提供各組件共享數(shù)據(jù)、共享邏輯
          │   ├── pages           // 主項(xiàng)目頁面目錄
          │   ├── ├── Project                 // 子項(xiàng)目頂級目錄
          │   ├── ├── ├── nft                 // NFT--PC版本項(xiàng)目目錄
          │   ├── ├── ├── ├── assets               // NFT--PC版本公共圖片文件
          │   ├── ├── ├── ├── components           // NFT--PC版本公共組件庫
          │   ├── ├── ├── ├── pages                // NFT--PC版本頁面目錄
          │   ├── ├── ├── mobile              // 移動端項(xiàng)目目錄
          │   ├── ├── ├── ├── nft-mobile           // NFT--微信版本項(xiàng)目目錄
          │   ├── ├── ├── ├── ├── assets              //  NFT--微信版本公共圖片文件
          │   ├── ├── ├── ├── ├── components          //  NFT--微信版本公共組件庫
          │   ├── ├── ├── ├── ├── pages               //  NFT--微信版本頁面目錄
          │   ├── 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
          

          安裝依賴

          $ yarn
          

          啟動注意事項(xiàng)

          依賴安裝完成后,src/setupProxy.js 為項(xiàng)目本地代理文件,請及時更改您需要代理的接口地址!

          !??!微信版本只可以在微信打開運(yùn)行,并直接在 router/AuthRoute.jsx 中填入自己申請的 appid,目前為''

          此為兩個項(xiàng)目依托同一底層框架,PC 及微信端口分離,如需切換請?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)目名稱。

          兩套 PRE_ROUTER.js 中配置分別如下:

          微信版本: 路由前綴----/front_nft_mobile 請求地址前綴----/api/nft 登錄頁面----/nft_mobile_home

          PC 版本: 路由前綴----/front_nft_pc 請求地址前綴----/api/nft 登錄頁面----/nft_home

          開發(fā)啟動

          $ yarn start
          
          #指定端口
          $ PORT=8080 yarn start
          
          # HTTPS方式啟動
          $ HTTPS=true yarn start
          

          生產(chǎn)構(gòu)建

          $ yarn build
          
          // 構(gòu)建輸入到指定目錄
          $ BUILD_PATH=../dist yarn build
          

          域名子目錄發(fā)布項(xiàng)目

          // 開發(fā)啟動
          $ BASE_NAME=/synext-admin yarn start
          
          // 開發(fā)訪問
          'http://localhost:XXXX/synext-admin/'
          
          //生產(chǎn)環(huán)境 同上
          $ BASE_NAME=/synext-admin yarn start
          // 訪問
          'http://xxx.com/synext-admin'
          

          菜單配置

          //在/src/menus.js文件中配置菜單數(shù)據(jù),前端硬編碼或異步加載菜單數(shù)據(jù)。
          // 菜單支持頭部、左側(cè)、頭部+左側(cè)三種布局方式,默認(rèn)左側(cè)菜單。如需放開設(shè)置,請到'src/layouts/index.jsx'放開注釋
          //菜單字段說明。
          字段	必須	說明
          key	    	//需要唯一
          parentKey		//用于關(guān)聯(lián)父級
          path		//菜單對應(yīng)的路由地址
          text		//菜單標(biāo)題
          icon		//菜單圖標(biāo)配置
          url	    	//菜單對應(yīng)會打開url對應(yīng)的iframe頁面,如果配置了url,path將無效
          target		//配合url使用,菜單將為a標(biāo)簽 <a href={url} target={target}>{text}</a>
          order		//菜單排序,數(shù)值越大越靠前顯示
          type		//如果菜單數(shù)據(jù)中攜帶功能權(quán)限配置,type==='1' 為菜單,type==='2'為功能
          code		//功能碼,如果是type==='2',會用到此字段
          

          頁面開發(fā)

          //配置組件
          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(...)
              }
              ...
          }
          
          參數(shù) 類型 默認(rèn)值 說明
          noFrame boolean false 標(biāo)記當(dāng)前頁面為不需要導(dǎo)航框架的頁面,比如登錄頁,通過腳本抓取實(shí)現(xiàn)
          noAuth boolean false 標(biāo)記當(dāng)前頁面為不需要登錄即可訪問的頁面,通過腳本抓取實(shí)現(xiàn)
          keepAlive boolean - 標(biāo)記當(dāng)前頁面內(nèi)容在頁面切換之后是否保持
          title boolean 或 string 或 ReactNode 或 object 或 function(props) true true:當(dāng)前頁面顯示通過菜單結(jié)構(gòu)自動生成的 title;false:當(dāng)前頁面不顯示 title;string:自定義 title;object:{text,icon} text 為顯示的名稱,icon 為圖標(biāo);function(props): 返回值作為 title
          breadcrumbs boolean 或 array 或 function(props) true true:當(dāng)前頁面顯示通過菜單結(jié)構(gòu)自動生成的面包屑;false:當(dāng)前頁面不顯示面包屑;object:[{icon, text, ...}];function(props): 返回值作為面包屑
          appendBreadcrumbs array 或 function(props) [] 在當(dāng)前面包屑基礎(chǔ)上添加;function(props): 返回值作為新添加的面包屑
          pageHead boolean - 頁面頭部是否顯示
          side boolean - 頁面左側(cè)是否顯示
          sideCollapsed boolean - 左側(cè)是否收起
          ajax boolean true 是否添加 ajax 高階組件,內(nèi)部可以通過 this.props.ajax 使用 ajax API,組件卸載時,會自動打斷未完成的請求
          router boolean false 是否添加 withRouter 裝飾器,組件內(nèi)部可以使用 this.props.history 等 API
          query boolean false 是否添加地址查詢字符串轉(zhuǎn)換高階組件,內(nèi)部可以通過 this.props.query 訪問查詢字符串
          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 事件,并在組件卸載時會自動清理;通過 this.props.removeEventListener 移出 dom 事件
          pubSub boolean false 是否添加發(fā)布訂閱高階組件,可以使用 this.props.subscribe(topic, (msg, data) => {...})訂閱事件,并在組件卸載時,會自動取消訂閱; 通過 this.props.publish(topic, data)發(fā)布事件
          modal string 或 object false 當(dāng)前組件是否是 modal。string: 彈框標(biāo)題;object:彈框配置

          注:

          • noFrame、noAuthkeepAlive 只有配置了path才有效!
          • config 裝飾器可以用于任何組件,但是title、breadcrumbs、appendBreadcrumbspageHead、side、sideCollapsed最好在路由對應(yīng)的頁面組件中使用
          //頁面保持
          //頁面渲染一次之后會保持狀態(tài),再次跳轉(zhuǎn)到此頁面不會重新創(chuàng)建或重新渲染
          開啟方式
          1.  /src/models/system.js initState.keepAlive 屬性修改默認(rèn)值
          2.  config裝飾器 keepAlive屬性
          

          頁面顯示/隱藏事件

          config 裝飾器為組件注入了兩個事件 onComponentWillShow、onComponentWillHide ,如果頁面使用了 Keep Alive 功能,切換顯示/隱藏時會觸發(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
                  });
              }
              ...
          }
          

          頁面容器 PageContent

          系統(tǒng)提供了頁面的跟節(jié)點(diǎn) PageContent,有如下特性:

          • 添加了 margin padding 樣式;
          • 添加了 footer;
          • 支持頁面 loading;
          • 自動判定是否有底部工具條 FixBottom 組件,為底部工具條騰出空間;

          是否顯示 footer,默認(rèn) true

          <PageContent footer={false}>...</PageContent>
          

          顯示 loading,有兩種方式。

          1. model 方式

            this.props.action.page.showLoading();
            this.props.action.page.hideLoading();
             
          2. props 方式

            const { loading } = this.state;
            
            <PageContent loading={loading}>...</PageContent>;
            

          彈框頁面開發(fā)

          添加、修改等場景,往往會用到彈框,antd Modal 組件使用不當(dāng)會產(chǎn)生臟數(shù)據(jù)問題(兩次彈框渲染數(shù)據(jù)互相干擾)

          系統(tǒng)提供了基于 modal 封裝的高階組件,每次彈框關(guān)閉,都會銷毀彈框內(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ù)說明如下:

          1. 如果是 string,作為 modal 的 title
          2. 如果是函數(shù),函數(shù)返回值作為 Modal 參數(shù)
          3. 如果是對象,為 Modal 相關(guān)配置,具體參考 antd Modal 組件
          4. options.fullScreen boolean 默認(rèn) false,是否全屏顯示彈框

          ModalContent 組件

          彈框內(nèi)容通過 ModalContent 包裹,具體參數(shù)如下:

          參數(shù) 類型 默認(rèn)值 說明
          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,通過 route-loader 將路由內(nèi)容填充到/src/pages/page-routes.js 文件,支持兩種寫法:

          1. 常量方式
            export const PAGE_ROUTE = "/path";
             
          2. 頁面 config 裝飾器(推薦)
            @config({
                path: '/path',
            })
            export default class SomePage extends React.Component {
                ...
            }
            

          二級頁面

          二級頁面如果要保持父級菜單的選中狀態(tài),以父級 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 請求

          系統(tǒng)的 ajax 請求基于 axios 封裝。 基于 restful 規(guī)范,提供了 5 個方法:

          • 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對象
          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 方式做了封裝,頁面被卸載之后會自動打斷未完成的請求

          接收參數(shù)

          所有的 ajax 方法參數(shù)統(tǒng)一,都能夠接受三個參數(shù): 參數(shù)|說明 ---|--- url|請求地址 params|請求傳遞給后端的參數(shù) options|請求配置,即 axios 的配置。

          options 配置

          參數(shù) 說明
          axios 配置 可以接受 axios 參數(shù)
          successTip 擴(kuò)展的參數(shù),成功提示
          errorTip 擴(kuò)展的參數(shù),失敗提示
          noEmpty 擴(kuò)展的參數(shù),過濾掉 ''、null、undefined 的參數(shù),不提交給后端
          originResponse 擴(kuò)展參數(shù),.then 中可以拿到完整的 response,而不只是 response.data

          注:全局默認(rèn)參數(shù)可以在 src/commons/ajax.js 中進(jìn)行配置,默認(rèn) baseURL='/api'、timeout=1000 * 60。

          請求結(jié)果提示

          1. 系統(tǒng)對 ajax 失敗做了自動提示,開發(fā)人員可通過 src/commons/handle-error.js 進(jìn)行配置;
          2. 成功提示默認(rèn)不顯示,如果需要成功提示,可以配置 successTip 參數(shù),或者.then()中自行處理;
          3. 成功提示在 src/commons/handle-success.js 中配置;
          this.props.ajax.del("/user/1", null, {
              successTip: "刪除成功!",
              errorTip: "刪除失??!",
              noEmpty: true,
          });
          

          loading 處理

          系統(tǒng)擴(kuò)展了 promise,提供了 finally 方法,用于無論成功還是失敗,都要進(jìn)行的處理。一般用于關(guān)閉 loading

          this.setState({loading: true});
          this.props.ajax
              .get('/url')
              .then(...)
              .finally(() => this.setState({loading: false}));
          

          Mock 模擬數(shù)據(jù)

          前后端并行開發(fā),為了方便后端快速開發(fā),不需要等待后端接口,系統(tǒng)提供了 mock 功能?;?a >mockjs

          編寫模擬數(shù)據(jù)

          在/src/mock 目錄下進(jìn)行 mock 數(shù)據(jù)編寫,比如:

          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 接口編寫,系統(tǒng)提供了簡化腳本(/src/mock/simplify.js),上面的例子就是簡化寫法

          對象的 key 由 method url delay,各部分組成,以空格隔開

          字段 說明
          method 請求方法 get post 等
          url 請求的 url
          delay 模擬延遲,毫秒 默認(rèn) 1000

          調(diào)用

          系統(tǒng)封裝的 ajax 可以通過以下兩種方式,自動區(qū)分是 mock 數(shù)據(jù),還是真實(shí)后端數(shù)據(jù),無需其他配置

          mock 請求:

          • url 以/mock/開頭的請求
          • /src/mock/url-config.js 中配置的請求
          this.props.ajax.get('/mock/users').then(...);
          

          如果后端真實(shí)接口準(zhǔn)備好之后,去掉 url 中的/mock 即可

          注:mock 功能只有開發(fā)模式下開啟了,生產(chǎn)模式不會開啟 mock 功能,如果其他環(huán)境要開啟 mock 使用 MOCK=true 參數(shù),比如 MOCK=true yarn build

          樣式

          系統(tǒng)使用less進(jìn)行樣式的編寫。 為了避免多人合作樣式?jīng)_突,系統(tǒng)對 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>;
              }
          }
           

          注:基礎(chǔ)組件不使用 Css Module,不利于樣式覆蓋;

          主題

          使用 less,通過樣式覆蓋來實(shí)現(xiàn)。

          編寫主題

          • less 文件中使用主題相關(guān)變量;
          • 編寫/src/theme.less通過less-loadermodifyVars覆蓋 less 中的變量;

          注:目前每次修改了 theme.less 需要重新 yarn start 才能生效

          參考

          導(dǎo)航布局

          為了滿足不同系統(tǒng)的需求,提供了四種導(dǎo)航布局:

          • 頭部菜單
          • 左側(cè)菜單
          • 頭部+左側(cè)菜單
          • tab 頁方式

          更改方式

          • 用戶可以通過 頁面有上角用戶頭像 -> 設(shè)置 頁面進(jìn)行選擇(如果您為用戶提供了此頁面);
          • 開發(fā)人員可以通過修改src/models/index.js指定布局方式;

          不需要導(dǎo)航

          有些頁面可能不需要顯示導(dǎo)航,可以通過如下任意一種方式進(jìn)行設(shè)置:

          • 頁面配置高級組件
            @config({
                noFrame: true,
            })
             
          • 瀏覽器 url 中 noFrame=true 參數(shù)
            /path/to?noFrame=true
            

          Tab 標(biāo)簽頁

          頁面頭部標(biāo)簽,有如下特性:

          1. 在當(dāng)前 tab 標(biāo)簽之后打開新的 tab 標(biāo)簽;
          2. 記錄并恢復(fù)滾動條位置;
          3. 保持頁面狀態(tài)(需要開啟Keep Page Alive);
          4. tab 標(biāo)簽右鍵操作;
          5. tab 頁操作 API;
          6. tab 標(biāo)簽拖拽排序;
          7. 關(guān)閉一個二級頁面 tab,嘗試打開它的父級;

          Tab 操作 API

          system model(redux)中提供了如下操作 tab 頁的方法:

          API 說明
          setCurrentTabTitle(title) 設(shè)置當(dāng)前激活的 tab 標(biāo)題 title: stirng 或 {text, icon}
          refreshTab(targetPath) 刷新 targetPath 指定的 tab 頁內(nèi)容(重新渲染)
          refreshAllTab() 刷新所有 tab 頁內(nèi)容(重新渲染)
          closeCurrentTab() 關(guān)閉當(dāng)前 tab 頁
          closeTab(targetPath) 關(guān)閉 targetPath 對應(yīng)的 tab 頁
          closeOtherTabs(targetPath) 關(guān)閉除了 targetPath 對應(yīng)的 tab 頁之外的所有 tab 頁
          closeAllTabs() 關(guān)閉所有 tab 頁,系統(tǒng)將跳轉(zhuǎn)首頁
          closeLeftTabs(targetPath) 關(guān)閉 targetPath 對應(yīng)的 tab 頁左側(cè)所有 tab 頁
          closeRightTabs(targetPath) 關(guān)閉 targetPath 對應(yīng)的 tab 頁右側(cè)所有的 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');
              }
              ...
          }
          

          注:

          1. tab 基于頁面地址,每當(dāng)使用this.props.history.push('/some/path'),就會選中或者新打開一個 tab 頁(/path  /path?name=Tom屬于不同 url 地址,會對應(yīng)兩個 tab 頁);
          2. 沒有菜單對應(yīng)的頁面,需要單獨(dú)設(shè)置 title,否則 tab 標(biāo)簽將沒有 title;

          models(redux) 封裝

          基于redux進(jìn)行封裝,不改變 redux 源碼,可以結(jié)合使用 redux 社區(qū)中其他解決方案。

          注:一般情況下,用不到 redux~

          models 用于管理數(shù)據(jù),解決的問題:

          1. 命名空間(防止數(shù)據(jù)、方法命名沖突):數(shù)據(jù)與方法,都?xì)w屬于具體 model,比如:state.userCenter.xxx,this.props.action.userCenter.xxx();
          2. 如何方便的獲取數(shù)據(jù):connect 與組件連接;@connect(state => ({name: state.user.name}));
          3. 如何方便的修改數(shù)據(jù):this.props.action 中方法;
          4. 客戶端數(shù)據(jù)持久化(保存到 LocalStorage 中):syncStorage 配置;
          5. 異步數(shù)據(jù)處理:基于 promise 異步封裝;
          6. 請求錯誤提示:error 處理封裝,errorTip 配置,自動提示;
          7. 請求成功提示:successTip 配置,自動提示;
          8. 簡化寫法:types actions reducers 可以在一個文件中編寫,較少沖突,方便多人協(xié)作,參見models/page.js中的寫法;
          9. 業(yè)務(wù)代碼可集中歸類:在 models 目錄中統(tǒng)一編寫,或者在具體業(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 />;
          };
          

          對 useSelector 的說明

          useSelector(select) 默認(rèn)對 select 函數(shù)的返回值進(jìn)行引用比較 ===,并且僅在返回值改變時觸發(fā)重渲染。

          即:如果 select 函數(shù)返回一個臨時對象,會多次 re-render

          最好不要這樣使用:
          const someData = useSelector(state => {
          
              // 每次都返回一個新對象,導(dǎo)致re-render
              return {name: state.name, age: state.age};
          })
          
          最好多次調(diào)用useSelector,單獨(dú)返回?cái)?shù)據(jù),或者返回非引用類型數(shù)據(jù)
          const name = useSelector(state => state.firstName + state.lastName);
          const age = useSelector(state => state.age);
          

          js 文件中使用

          沒有特殊需求,一般不會在普通 js 文件中使用

          import { action, store } from "src/models";
          
          // 獲取數(shù)據(jù)
          const state = store.getState();
          
          // 修改數(shù)據(jù)
          action.side.hide();
          

          簡化寫法

          action reducer 二合一,省去了 actionType,簡化寫法;

          注意:

          • 所有的 reducer 方法,無論是什么寫法中的,都可以直接返回新數(shù)據(jù),不必關(guān)心與原數(shù)據(jù)合并(...state),封裝內(nèi)部做了合并;
          • 一個 model 中,除了 initialState syncStorage actions reducers 等關(guān)鍵字之外的屬性,都視為 action reducer 合并寫法;

          一個函數(shù)

          一個函數(shù),即可作為 action 方法,也作為 reduce 使用

          • 調(diào)用 action 方法傳遞的數(shù)據(jù)將不會做任何處理,會直接傳遞給 reducer
          • 只能用第一個參數(shù)接收傳遞過來的數(shù)據(jù),如果多個數(shù)據(jù),需要通過對象方式傳遞,如果不需要傳遞數(shù)據(jù),但是要使用 state,也需要定義一個參數(shù)占位
          • 第二個參數(shù)固定為 state,第三個參數(shù)固定為 action,不需要可以缺?。ㄒ话愣际侨笔〉模?/li>
          • 函數(shù)的返回值為一個對象或者 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ù)同步

          通過配置的方式,可以讓 redux 中的數(shù)據(jù)自動與 localStorage 同步

          export default {
              initialState: {
                  title: '',
                  show: true,
                  user: {},
                  users: [],
                  job: {},
                  total: 0,
                  loading: false,
                  ...
              },
          
              // initialState會全部同步到localStorage中
              // syncStorage: true,
          
              // 配置部分存數(shù)據(jù)儲到localStorage中
              syncStorage: {
                  titel: true,
                  user: { // 支持對象指定字段,任意層次
                      name: true,
                      address: {
                          city: true,
                      },
                  },
                  job: true,
                  users: [{name: true, age: true}], // 支持?jǐn)?shù)組
              },
          }
          

          action reducer 合并寫法

          如果 action 有額外的數(shù)據(jù)處理,并且一個 action 只對應(yīng)一個 reducer,這種寫法不需要指定 actionType,可以有效簡化代碼;

          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)部封裝會與原state合并`{...state, ...newState}`;
                  },
              },
          };
          

          異步 action 寫法

          export default {
              initialState: {
                  title: '',
                  ...
              },
              fetchUser: {
                  // 異步action payload 返回promise
                  payload: ({params, options}) => axios.get('/mock/users', params, options),
          
                  // 異步action 默認(rèn)使用通用異步meta配置 commonAsyncMeta,對successTip errorTip onResolve onReject onComplete 進(jìn)行了合理的默認(rèn)值處理,需要action以對象形式傳參調(diào)用
                  // 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}),
                  }
              },
          };
          

          調(diào)用方式:

          this.props.action.user.fetchUser({
              params,
              options,
              successTip,
              errorTip,
              onResolve,
              onReject,
              onComplete,
          });
          

          參數(shù)約定為一個對象,各個屬性說明如下:

          參數(shù) 說明
          params 請求參數(shù)
          options 請求配置
          successTip 成功提示信息
          errorTip 錯誤提示信息
          onResolve 成功回調(diào)
          onReject 失敗回調(diào)
          onComplete 完成回調(diào),無論成功、失敗都會調(diào)用

          單獨(dú)定義 action 和 reducer

          支持這種比較傳統(tǒng)的寫法,一般也不會太用到

          import {createAction} from 'redux-actions';
          
          export const types = {
              GET_MENU_STATUS: 'MENU:GET_MENU_STATUS', // 防止各個模塊沖突,最好模塊名開頭
          };
          
          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能對應(yīng))
              reducers: {
                  [types.GET_MENU_STATUS](state) {
                      ...
                      return {
                          ...
                      };
                  }
              },
          }
          

          權(quán)限控制

          系統(tǒng)菜單、具體功能點(diǎn)都可以進(jìn)行權(quán)限控制。

          菜單權(quán)限

          菜單由后端提供(一般系統(tǒng)都是后端提供),后臺通過登錄用戶返回用戶的菜單權(quán)限;頁面只顯示獲取到的菜單;

          系統(tǒng)提供了一個基礎(chǔ)的菜單、權(quán)限管理頁面,需要后端配合存儲數(shù)據(jù)。

          功能權(quán)限

          可以通過src/components/permission組件對功能的權(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>添加用戶</Button>
                          </Permission>
                      </div>
                  );
              }
          }
           

          注:權(quán)限的 code 前端使用時會硬編碼,注意語義化、唯一性。

          角色

          一般系統(tǒng)都會提供角色管理功能,系統(tǒng)中提供了一個基礎(chǔ)的角色管理功能,稍作修改即可使用。

          開發(fā)代理

          開發(fā)時,要與后端進(jìn)行接口對接,可以通過代理與后端進(jìn)行連接,開發(fā)代理配置在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, // 是否驗(yàn)證證書
                      ws: true, // 啟用websocket
                  })
              );
          };
          

          注:更多代理配置請參考http-proxy-middleware

          前端默認(rèn) ajax 前綴 /api 可以通過 AJAX_PREFIX 參數(shù)進(jìn)行修改。

          nginx 配置參考

          這里只是參考文件,根據(jù)自己的項(xiàng)目需求自行配置

          一個域名對應(yīng)單個項(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 防止頁面刷新出現(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請求 前端ajax請求以 /api 開頭
              location ^~/api {
                 rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是統(tǒng)一以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;
              }
          }
          

          一個域名對應(yīng)多個項(xiàng)目

          多個項(xiàng)目掛載到同一個域名下,可以通過子目錄方式區(qū)分

          比如,如下地址各對應(yīng)一個項(xiàng)目

          前端項(xiàng)目構(gòu)建時,添加 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)目錄 名稱與 location /project1 location ~ ^/project1/static/.*  配置對應(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)目配置,訪問地址 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請求 前端ajax請求以/api開頭
              location ^~/api {
                 rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是統(tǒng)一以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;
              }
          
              # 子項(xiàng)目配置 訪問地址 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請求 前端ajax請求以 /project1_api 開頭
              location ^~/project1_api {
                 rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是統(tǒng)一以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;
              }
          }
          

          其他

          頁面打印

          通過給元素添加相應(yīng)的 class,控制打印內(nèi)容:

          • .just-print 只在打印時顯示
          • .no-print 在打印時不顯示

          ESLint 說明

          如果前端項(xiàng)目,不是 git 根目錄,在提交的時候,會報(bào)錯 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
          • 方便路徑書寫,不必關(guān)心相對路徑結(jié)構(gòu)
          • 復(fù)制粘貼到其他文件,不必修改路徑

          支持判斷運(yùn)算符

          const name = res?.data?.user?.name || "匿名";
          

          form 表單

          1. 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'
          
          瀏覽 16
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  aⅴ中文字幕不卡在线无码 | 天天色天天操天天 | 三级网站在线麻豆 | 一级A爱爱 | 欧美Aa亚洲Aa国产 |