JS 全棧前端後臺管理部分筆記(2)- 富文本編輯、圖片上傳、接口鑑權

前言

使用vue和element搭建的管理後臺筆記。github地址:待補充(github在家裏打不開啊啊啊)

物品管理頁面

增加左側菜單導航入口:

  <el-submenu index="2">
          <template slot="title"><i class="el-icon-message"></i>物品管理</template>
          <el-menu-item-group>
            <template slot="title">分類</template>
            <el-menu-item index="/items/create">新建物品</el-menu-item>
            <el-menu-item index="/items/list">物品列表</el-menu-item>
          </el-menu-item-group>
        </el-submenu>

編輯和列表頁面:複製CategoryEdit.vue和CategoryList.vue爲Itemxxx,並修改對應內容。

增加路由:

{
        path: "/items/create",//增加物品
        component: ItemEdit
      },
      {
        path: "/items/edit/:id", //編輯物品
        component: ItemEdit,//編輯與增加使用同一個組件
        props: true, //Props爲true,表示將任何url參數都注入到 categoryedit的組件內
      },
      {
        path: "/items/list",//物品列表
        component: ItemList,
      },

進行測試,
20200208212446710

圖片上傳

圖片一般需要上傳到圖片服務器,這裏使用el-upload實現

//ItemEdit.vue
  <el-upload class="avatar-uploader" :action="fileUploadPath" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
          <img v-if="model.icon" :src="model.icon" class="avatar">
          <i v-else class="el-icon-plus avatar-uploader-icon"></i>
 </el-upload>
......
 //圖片上傳成功處理
handleAvatarSuccess(res) {
    this.$set(this.model, "icon", res.imgTrueUrl);
},
    //上傳文件類型及大小判斷
    beforeAvatarUpload(file) {
        const isJPG = file.type === "image/jpeg";
        const isLt2M = file.size / 1024 / 1024 < 2;

        if (!isJPG) {
            this.$message.error("上傳頭像圖片只能是 JPG 格式!");
        }
        if (!isLt2M) {
            this.$message.error("上傳頭像圖片大小不能超過 2MB!");
        }
        return isJPG && isLt2M;
    }
  • el-upload 使用自身的ajax來實現http的post方法,因此需要將圖片實際上傳地址賦值給 aciton ,這裏使用$http.defaults.baseURL+/upload``前半部分是我們定義在axios中的基本地址,後半部分是路徑,注意要給aciton前加冒號,表示動態綁定,否則只能傳入變量名稱字符串,在這裏卡了好久。。。

  • beforeAvatarUpload 是在圖片上傳前執行的函數,可以用來驗證圖片格式和大小,返回false圖片就不繼續上傳了。

  • handleAvatarSuccess 用於在圖片post成功後處理返回的圖片鏈接,這裏將圖片反顯。

  • 使用vue的setmodelset方法爲model對象屬性賦值,set類似於react 的setState。原因是 vue 框架data中定義對象model而沒有爲model內定義icon屬性,之後對model內屬性賦值( this.model.icon = res.imgTrueUrl)不會造成更新,所以使用$set.

英雄管理頁面

複製ItemEdit.vue和ItemList.vue到HeroEdit.vue和HeroList.vue並修改頁面內容,將接口請求資源改爲heros。

修改main.vue ,增加左側菜單入口 ,(與上類似,省略,下同)

修改路由文件。

根據前端頁面要求的字段修改後臺錄入頁面,與物品管理不同的是,英雄配置內容較多,用到了 el-tabs、el-select 等控件,而且在一個表單內容太多時,可以拆分爲兩個tab頁,這裏拆成了基礎信息和技能信息。

<el-form label-width="120px" @submit.native.prevent="save">
      <el-tabs type="border-card" value="skills">
        <el-tab-pane label="基礎信息" name="basic">
          <el-form-item label="名稱">
            <el-input v-model="model.name"></el-input>
          </el-form-item>
          <el-form-item label="頭像">
            <el-upload class="avatar-uploader" :action="fileUploadPath" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
              <img v-if="model.avatar" :src="model.avatar" class="avatar">
              <i v-else class="el-icon-plus avatar-uploader-icon"></i>
            </el-upload>
          </el-form-item>
          <el-form-item label="稱號">
            <el-input v-model="model.title"></el-input>
          </el-form-item>
          <el-form-item label="分類">
            <el-select v-model="model.categories" placeholder="請選擇" multiple>
              <el-option v-for="(item,index) in allCategories" :key="index" :label="item.name" :value="item._id">
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="難度">
            <el-rate style="margin-top:0.6rem" :max="9" v-model="model.scores.difficult"></el-rate>
          </el-form-item>
          <el-form-item label="技能">
            <el-rate style="margin-top:0.6rem" :max="9" v-model="model.scores.skills"></el-rate>
          </el-form-item>
          <el-form-item label="攻擊">
            <el-rate style="margin-top:0.6rem" :max="9" v-model="model.scores.attack"></el-rate>
          </el-form-item>
          <el-form-item label="生存">
            <el-rate style="margin-top:0.6rem" :max="9" v-model="model.scores.survive"></el-rate>
          </el-form-item>
          <el-form-item label="順風出裝">
            <el-select v-model="model.items1" placeholder="請選擇" multiple>
              <el-option v-for="(item,index) in Items" :key="index" :label="item.name" :value="item._id">
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="逆風出裝">
            <el-select v-model="model.items2" placeholder="請選擇" multiple>
              <el-option v-for="(item,index) in Items" :key="index" :label="item.name" :value="item._id">
              </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="使用技巧">
            <el-input type="textarea" v-model="model.usageTips">
            </el-input>
          </el-form-item>
          <el-form-item label="對抗技巧">
            <el-input type="textarea" v-model="model.battleTips">
            </el-input>
          </el-form-item>
          <el-form-item label="團戰技巧">
            <el-input type="textarea" v-model="model.teamTips">
            </el-input>
          </el-form-item>
        </el-tab-pane>
        <el-tab-pane label="技能" name="skills">
          <el-button type="primary" @click="model.skills.push({})"> <i class="el-icon-plus"></i>添加技能 </el-button>
          <!-- 注意這裏採用了flex佈局,動態增加方框內容,特別酷 -->
          <el-row type="flex" style="flex-wrap:wrap">
            <el-col :md="12" v-for="(item,index) in model.skills" :key="index">
              <el-form-item label='名稱'>
                <el-input v-model="item.name"></el-input>
              </el-form-item>
              <el-form-item label="圖片">
                <!-- 注意這裏上傳成功回調函數, -->
                <el-upload class="avatar-uploader" :action="fileUploadPath" :show-file-list="false" :on-success="res=>$set(item, 'icon',res.imgTrueUrl)" :before-upload="beforeAvatarUpload">
                  <img v-if="item.icon" :src="item.icon" class="avatar">
                  <i v-else class="el-icon-plus avatar-uploader-icon"></i>
                </el-upload>
              </el-form-item>
              <el-form-item label="描述">
                <el-input type="textarea" v-model="item.description">
                </el-input>
              </el-form-item>
              <el-form-item label="小提示">
                <el-input type="textarea" v-model="item.tips">
                </el-input>
              </el-form-item>
              <el-form-item>
                <!-- 刪除元素 -->
                <el-button type="danger" @click="model.skills.splice(index,1)">刪除</el-button>
              </el-form-item>
            </el-col>
          </el-row>
        </el-tab-pane>
      </el-tabs>

      <el-form-item>
        <el-button style="margin-top:15px" type="primary" native-type="submit"> 保存</el-button>
      </el-form-item>
    </el-form>
  • 每個el-tab-pane有一個 name,當在父級 el-tabs 設置的 value與name相同時,默認載入該標籤頁

  • flex佈局中 flex-warp:warp 用於指定換行

  • elementui中不像antd,可以直接寫 小寫形式的style 比如 style=“margin-top:10px”

  • 技能錄入中使用了一種非常酷的效果,點擊 新增技能 則動態生成一個技能卡片用來錄入信息。實現的原理是 技能卡片被el-col包裹,以v-for循環model.skills數組方式利用flex佈局展示在一個el-row中,其中el-row設置爲換行,當點擊 增加 按鈕時push到skills數組中一個新的空對象,完成新卡片的動態展示。刪除同理,利用splice函數將位於index的item切掉

  • 在技能卡片中有上傳圖片功能,上傳完畢後設置圖片回顯url使用$set實現, item.icon= res.imgTrueUrl無效,因爲沒有觸發重新渲染。

具體效果如下:

在這裏插入圖片描述

文章管理

與上述英雄相同,新增ArticleEdit.vue和ArticleList.vue文件,修改內容,新增左側菜單入口,新增路由入口。大體相同。唯一不同的是文章管理多了一個富文本編輯器

富文本編輯器

使用vue2-editor,安裝

npm install vue2-editor --save

基本使用

import { VueEditor } from "vue2-editor";
......
<el-form-item label="內容">
    <vue-editor v-model="model.body"></vue-editor>
</el-form-item>
  • 導入組件
  • v-model用於綁定富文本編輯器顯示的內容

通過利用瀏覽器工具查看頁面元素不難發現,富文本編輯器對文字樣式的操作就是對html標籤的操作,比如加粗,就是在選中的文字上加入了strong標籤,保存到數據庫時就是將編輯的文檔以html形式保存到數據中。

圖片自定義上傳位置

當vue2-editor插入一個圖片時通過瀏覽器工具可以發現他是將圖片轉在base64編碼在本地進行顯示,當提交到數據庫時也是將base64編碼作爲html中的一個字符串提交。如下

在這裏插入圖片描述

在這裏插入圖片描述

base64上傳下載會導致接口通信數據載荷太大,如上179KB,這樣是不合理的。

一般這種類型是將圖片上傳到專門的圖片服務器,然後將圖片地址保存到數據庫中,和之前所做的相同,所以我們要修改編輯器對圖片的處理方式。

<el-form-item label="內容">
        <vue-editor useCustomImageHandler @image-added="handleImageAdded" v-model="model.body"></vue-editor>
      </el-form-item>
......
async handleImageAdded(file, Editor, cursorLocation, resetUploader) {
      var formData = new FormData();
      formData.append("file", file);
      let res = await this.$http.post("upload", formData);
      Editor.insertEmbed(cursorLocation, "image", res.data.imgTrueUrl);
      resetUploader();
    }
  • vue2-editor 提供 @image-added 鉤子函數來自定義上傳方式,handleImageAdded入參分別是 選擇的文件、編輯器、光標當前位置和一個復位函數。
  • FormData是html中自帶對象,用來生成表單數據。我們利用原先的接口來上傳圖片,file是接口中定義圖片的字段。

用戶管理

與上述相同,增加頁面,修改路由,增加菜單入口,不在贅述

登錄頁面

新建登錄文件Login.vue,然後 使用el-card來實現頁面,如下:

<template>
  <div class="login-container">
    <el-card header="請先登錄" class="login-card">
      <el-form @submit.native.prevent="login">
        <el-form-item label="用戶名">
          <el-input v-model="model.username"></el-input>
        </el-form-item>
        <el-form-item label="密碼">
          <el-input v-model="model.password" type="password"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" native-type="submit">登錄</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>
<script>
export default {
  data() {
    return {
      model: {}
    };
  },
  methods: {
    async login() {
      const res = await this.$http.post("login", this.model);
      //保存token
      localStorage.token = res.data.token;
      //跳轉首頁
      this.$router.push("/");
      //提示登錄成功
      this.$message({
        type: "success",
        message: "登錄成功"
      });

    }
  }
};
</script>

<style >
.login-card {
  width: 30rem;
  margin: 50px auto;
}
</style>
  • login函數實現登錄接口請求並將返回的token保存在localstorage中

接口請求鑑權

後端設置每次接口請求都要驗證是否具有token來判斷請求的合法性,因此我們要在每一個請求上加上Authorization認證信息,爲了簡化操作,可以使用axios自帶的請求攔截器,如下:

http.interceptors.request.use((config) => {
  if (localStorage.token) {
    config.headers.Authorization = "bearer " + localStorage.token; //在請求頭增加token,其中Authorzation是規定的認證頭 bearer是業界通用的字符,注意後邊有一個字符
  }
  return config;
}, (error) => {
  return Promise.reject(error)
})
  • 請求攔截器實現只要是axios發出的請求,都會

上傳文件鑑權

由於後端nodejs在每個接口都加了token鑑權,而在axios的intercepter攔截中加的headers設置不會生效到el-upload組件中,因爲el-upload組件使用了自己實現的ajax來進行http請求,所以這裏需要爲el-upload配置一個headers。

爲方便獲取localStorage中的token,使用到了Vue的mixin ,如下:

//main.js
Vue.mixin({
  computed: {
    uploadUrl() {//上傳文件的url
      return this.$http.defaults.baseURL + "/upload"
    }
  },
  methods: {
    getAuthHeaders() {
      return { Authorization: `Bearer ${localStorage.token}` }
    }
  }
})
  • 定義mixin(混入) 必須在new vue之前
  • mixin定義之後,可以在Vue任意地方訪問了,
  • mixin的定義和vue組件的定義相同,包括methods、computed等

使用mixin

//ItemEdits.js    
<el-form-item label="圖標">
        <!-- 注意uploadUrl和 getAuthHeaders 是定義在 main.js中的mixin -->
        <el-upload class="avatar-uploader" :action="uploadUrl" :headers="getAuthHeaders()" :show-file-list="false" :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
          <img v-if="model.icon" :src="model.icon" class="avatar">
          <i v-else class="el-icon-plus avatar-uploader-icon"></i>
        </el-upload>
      </el-form-item>
  • 圖片上傳使用el-upload中headers來設置http請求的header,這裏用到了mixin中定義的函數,返回token。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章