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的 s e t 方 法 为 m o d e l 对 象 属 性 赋 值 , set方法为model对象属性赋值, setmodelset类似于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。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值