後臺項目總結

以前在公司主要負責後臺項目,就趁着即將離職回顧和總結本身踩過的坑javascript

目錄結構

初始化目錄使用的是 Vue CLI 生成的,可是默認的目錄結構不能知足對項目需求,下面是拓展後的結構css

├── public                          // 靜態頁面
├── scripts                         // 相關腳本配置
├── src                             // 主目錄
    ├── assets                      // 靜態資源
    ├── api                         // 接口信息
    ├── filters                     // 過濾
    ├── lib                         // 全局插件
    ├── router                      // 路由配置
    ├── store                       // vuex 配置
    ├── styles                      // 樣式
    ├── utils                       // 工具方法(axios封裝,全局方法等)
    ├── views                       // 頁面
    ├── App.vue                     // 頁面主入口
    ├── main.js                     // 腳本主入口
├── tests                           // 測試用例
├── .editorconfig                   // 編輯相關配置
├── .postcssrc.js                   // postcss 配置
├── babel.config.js                 // preset 記錄
├── package.json                    // 依賴
├── .eslintrc.js                    // eslint相關配置
├── README.md                       // 項目 readme
└── vue.config.js                   // webpack 配置
複製代碼

權限管理

一箇中大型的後臺必然少不了權限的控制,其中RBAC模型是須要了解的,更復雜的模型就是在此基礎上演化而來的,由於以前寫過相關文章,這裏就不具體介紹了,能夠點擊查看漫談一下權限設計相關html

axios 和 mock

axios

這裏axios是重點說的部分,axios 是一個 HTTP 庫,能夠用在瀏覽器和 node.js 中,在使用 srr 同構的時候也會常用它,下面說一下基本的封裝方式,具體狀況根據業務調整。前端

// utils/config.js
import http from "http";
import https from "https";
import qs from "qs";

const axiosConfig = {
  // 注意生產環境下要區分,這裏只作演示
  baseURL: "/mock/",
  // 請求後的數據處理
  transformResponse: [
    function(data) {
      return data;
    }
  ],
  // 查詢對象序列化函數
  paramsSerializer: function(params) {
    return qs.stringify(params);
  },
  // 超時設置
  timeout: 30000,
  // 跨域是否帶Token
  withCredentials: true,
  responseType: "json",
  // xsrf 設置
  xsrfCookieName: "XSRF-TOKEN",
  xsrfHeaderName: "X-XSRF-TOKEN",
  // 最多轉發數,用於node.js
  maxRedirects: 5,
  // 最大響應數據大小
  maxContentLength: 2000,
  // 自定義錯誤狀態碼範圍
  validateStatus: function(status) {
    return status >= 200 && status < 300;
  },
  // 用於node.js
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true })
};

export default axiosConfig;
複製代碼

上面定義的是配置文件,之因此不全局調用axios或者修改配置的緣由是會形成污染,下面還須要對攔截器這塊作下處理,取消重複請求(若是有業務代碼也能夠在裏面配置,好比須要統一發送token等參數)。vue

// utils/api.js
import axios from "axios";
import config from "./config";

// 取消重複請求
let pending = [];
const cancelToken = axios.CancelToken;
const removePending = config => {
  for (let p in pending) {
    let item = p;
    let list = pending[p];
    // 當前請求在數組中存在時執行函數體
    if (list.url === config.url + "&request_type=" + config.method) {
      // 執行取消操做
      list.cancel();
      // 從數組中移除記錄
      pending.splice(item, 1);
    }
  }
};

const service = axios.create(config);

// 添加請求攔截器
service.interceptors.request.use(
  config => {
    removePending(config);
    config.cancelToken = new cancelToken(c => {
      pending.push({
        url: config.url + "&request_type=" + config.method,
        cancel: c
      });
    });
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 返回狀態判斷(添加響應攔截器)
service.interceptors.response.use(
  res => {
    removePending(res.config);
    return res;
  },
  error => {
    return Promise.reject(error);
  }
);

export default service;
複製代碼

OK,基本封裝到這一步就說完了,在具體開發中能夠在經過引入vuexAction來調用axiosjava

mock

mock 在先後端開發中很常見,爲了解耦與後端以前沒必要要的等待時間,經過約定文檔接口的形式,讓前端能夠快速開發。node

這裏簡單說一下如何配置,具體的接口等信息根據業務來調整,首先在src建立一個mock文件夾,做爲 mock 存放的信息,若是業務分爲多層記得作好劃分,以後經過暴露index.js文件,在src/main.js中全局引入,下面是一個簡單的 demo 例子webpack

// src/mock/index.js
import Mock from "mockjs";
// 獲取 mock.Random 對象
const Random = Mock.Random;
// 設置異步等待時間
Mock.setup({
  timeout: "200-2000"
});
const produceNewsData = function() {
  let newNewsObject = {
    title: Random.ctitle(), // Random.ctitle( min, max ) 隨機產生一箇中文標題,長度默認在3-7之間
    content: Random.cparagraph(), // Random.cparagraph(min, max) 隨機生成一箇中文段落,段落裏的句子個數默認3-7個
    createdTime: Random.date() // Random.date()指示生成的日期字符串的格式,默認爲yyyy-MM-dd;
  };

  return newNewsObject;
};
// 隨便演示的請求圖片
const demoOther = function() {
  let newsList = [];
  for (let i = 0; i < 20; i++) {
    let newNewsObject = {
      img: Random.dataImage("300x250")
    };
    newsList.push(newNewsObject);
  }

  return newsList;
};
// 請求這個url就會被mock
Mock.mock("/mock/user", produceNewsData);
Mock.mock("/mock/other", demoOther);
複製代碼
// src/main.js
// 省略一下其餘代碼
import "./mock";
複製代碼

上面封裝axios配置文件config.js下填寫了baseURL,因此能夠直接省略mock的前綴,注意記得區分開發和生產環境ios

全局 loading

先說一下常見的 loading 集中管理方式git

  • 手動管理,每一個頁面引入一個 loading,在即將發送的請求的時候展開,請求結束關閉,除了囉嗦和修改麻煩以外沒有問題
  • 經過一些中間件來完成攔截,例如dav-loading
  • 經過攔截器攔截,例如axios

這裏介紹第三種的具體實現,緣由在因而後臺項目 loading 實質上只有一種,因此沒有必要引入第二種影響劃分組件的靈活性。

實現思路很簡單,藉助api目錄的劃分將須要 loading 的接口單獨放置在一個文件中,經過axios請求、響應攔截器匹配url完成顯示隱藏 loading,下面是具體的實現

// src/utils/app.js
import axios from "axios";
import config from "./config";
import vue from "vue";
import * as loadingAPI from "@/api/loading";

// 經過這個變化,來提醒loading顯示與否
const state = vue.observable({ content: 0 });

function changeState(config, add = true) {
  const { url } = config;
  const value = Object.values(loadingAPI);

  if (!value.includes(url)) {
    return;
  }
  if (add) {
    state.content += 1;
    return;
  }
  state.content -= 1;
  return;
}

const service = axios.create(config);

// 添加請求攔截器
service.interceptors.request.use(
  config => {
    changeState(config);
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// 返回狀態判斷(添加響應攔截器)
service.interceptors.response.use(
  res => {
    changeState(res.config, false);
    try {
      const { data } = res;
      Reflect.set(res, "data", JSON.parse(data));
    } catch (e) {
      //
    }
    return res;
  },
  error => {
    return Promise.reject(error);
  }
);
// 省略一些代碼
export default service;
export { state };
複製代碼

藉助vue.observable生成一個可供組件用於渲染函數和計算屬性內的對象,經過改變這個對象內部content值來完成 loading 的顯示和隱藏, 上面引入的 api 接口就是所有須要管理 loading 的接口地址,這也是上面爲何強調須要單獨劃分api目錄的緣由,以後再頁面中引入 loading組件 和state對象

<template>
  <div>
    <loading v-model="show" />
    <router-view class="router"></router-view>
  </div>
</template>

<script> import loading from "./loading"; import { state } from "@/utils/server"; export default { components: { loading }, data: () => ({ state }), computed: { show() { return this.state.content > 0; } } }; </script>
複製代碼

具體 loading 組件實現細節這裏就跳過了,最後說一下頁面的請求直接像正常頁面一致便可,例如

serve.get(user).then(user => {
  this.content = user.data.content;
  this.title = user.data.title;
});
// 一些其餘操做不會影響到loading的
Promise.all([serve.get(other)]).then(all => {
  console.log(all);
});
複製代碼

虛擬列表

聽名字可能有點不知所然,拋磚引玉以前寫項目遇到的一個場景,在後臺項目(根據業務)圖片管理功能可能常常遇到,後端返回給你必定數量的圖片的地址而後你滾動加載這些信息,這段邏輯很簡單,不過須要思考這樣會不會有性能上的問題?顯然會的,當滾動的數量足夠多的時候,以前的dom 節點一直存在就會形成瀏覽器的卡頓(吐槽一下知乎的無限滾動)。

虛擬列表解決的就是相似的場景,用戶感知的區域永遠是有限的,只須要在須要變化的時候更換展現的數據就能夠了,這裏貼一下以前寫的文章淺談一下列表優化

路由組件自定義緩存

一個很常見的場景就是在電商頁面中,當你在列表中點擊進入詳情在返回的時候,列表信息必定是存在沒有被清空的,也就是

主頁->列表->詳情

在列表前往詳情的時候返回咱們顯然但願這個頁面的信息不回被清空,從列表返回到主頁再進入列表,這一過程列表的信息不但願保留,下面就聊聊如何實現

keep-alive

vue 內置了組件keep-alive,它的做用就是緩存組件的信息,在官方指南中它和動態組件結合節省了沒必要要的渲染,同時它也是能夠與<router-view></router-view>結合作到緩存路由頁面的。

<keep-alive>
  <router-view></router-view>
</keep-alive>
複製代碼

不過單純使用和這個只能作到緩存,可是卻作不到咱們要求的指定某個頁面緩存,還有其餘方法麼?

其實也是有的能夠定義兩個<router-view/>根據標誌來區分是否須要緩存,而後利用keep-alive內置組件的include屬性動態調整就能夠實現咱們的需求了。

說的有點抽象了,先看一個例子吧

1

// route
// 省略部分代碼
 {
      path: "/keepAlive/",
      component: keepAlive,
      children: [
        {
          path: "",
          component: () =>
            import(/* webpackChunkName: "keep-alive-home" */ "@/view/keep-alive/home"),
          name: "keepAlive",
          meta: {
            depth: 1
          }
        },
        {
          path: "list",
          component: () =>
            import(/* webpackChunkName: "keep-alive-list" */ "@/view/keep-alive/list"),
          name: "list",
          meta: {
            name: "list",
            keepAlive: true,
            depth: 2
          }
        },
        {
          path: "details",
          component: () =>
            import(/* webpackChunkName: "keep-alive-details" */ "@/view/keep-alive/details"),
          name: "details",
          meta: {
            depth: 3
          }
        }
      ]
    },
複製代碼

上面的路由信息keepAlive表明是否須要緩存,depth表明深度,是咱們對比的時候使用,在定義<router-view/>的 vue 文件內,經過監聽$route來實現具體邏輯

// index.vue
<template>
  <!-- 定義兩個出口,一個能夠被緩存,一個普通路由 -->
  <div>
    <keep-alive :include="include">
      <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive"></router-view>
  </div>
</template>

<script> export default { data: () => ({ include: [] }), watch: { $route(to, from) { // 判斷進入的狀況 const { keepAlive, name, depth } = to.meta; if (keepAlive && !this.include.includes(name)) { this.include.push(name); } // 判斷回退的狀況 if (from.meta.keepAlive && from.meta.depth > depth) { // 刪除 const index = this.include.indexOf(from.meta.name); if (index !== -1) { this.include.splice(index, 1); } } } } }; </script>
複製代碼

這裏須要注意一點! 就是作緩存的頁面必定要有name屬性值

include: 匹配首先檢查組件自身的 name 選項,若是 name 選項不可用,則匹配它的局部註冊名稱 (父組件 components選項的鍵值)。匿名組件不能被匹配。

上述爲了方便管理,將組件的 name 屬性和路由的meta屬性作到了一致

最後

上面演示的例子,放到了個人倉庫,有興趣能夠 clone 下來本身調試。

我本人正在求職,若是小夥伴有合適的崗位拜託幫忙內推下,具體簡歷能夠私聊評論我,或者聯繫個人郵箱yangboses@gmail.com

相關文章
相關標籤/搜索