【Vue】项目:尚品汇后台管理系统(上)

前言

记录尚品汇后台管理系统的开发过程,功能模块包括登录、首页、品牌管理、平台属性管理、SKU管理、SPU管理、用户管理、角色管理、菜单管理模块。后台管理系统是CMS内容管理系统的一个子集,通过项目实战可以彻底搞明白菜单权限、按钮权限如何实现,掌握市场中数据可视化ECharts、V-charts的运用。

主要涵盖的技术点:Vue-cli、Axios、Vuex、Element-UI、菜单权限、按钮权限、数据可视化、Scss……

由于项目写的太长了,编辑器真的很卡呜呜呜,因此将项目分成了上、中、下三部分.
【Vue】项目:尚品汇后台管理系统(中)
【Vue】项目:尚品汇后台管理系统(下)


文章目录

  • 前言
  • 一、项目介绍
  • 二、项目配置
  • 三、完成登录业务
  • 四、退出登录业务
  • 五、项目路由的搭建
  • 六、完成品牌管理(tradeMark)静态组件
  • 七、完成品牌管理列表的展示
  • 八、添加品牌与修改品牌的静态组件
  • 九、完成添加品牌功能
  • 十、完成品牌修改功能
  • 十一、表单验证功能
  • 十二、删除品牌的操作


一、项目介绍

1.开发后台管理系统项目也是网页,用于进行信息系统管理的应用。类似学生管理系统等。

2.后台管理项目有一些模板可以使用,能够方便程序员搭建项目框架。

简洁版: https://github.com/PanJiaChen/vue-admin-template (本次使用)
加强版: https://github.com/PanJiaChen/vue-element-admin
(如果github进不去可以在gitee上下载:https://gitee.com/panjiachen/vue-admin-template?_from=gitee_search)

3.模板的文件与文件夹的认知【简洁版】

  • build ----index.js webpack配置文件【很少修改这个文件】

  • mock ----mock数据的文件夹【模拟一些假的数据mockjs实现的】,因为实际开发的时候,利用的是真是接口

  • node_modules ------项目依赖的模块

  • public ------ico图标,静态页面,publick文件夹里面经常放置一些静态资源,而且在项目打包的时候webpack不会编译这个文件夹,原封不动的打包到dist文件夹里面

  • src
    -----程序员源代码的地方
    -----api文件夹:涉及请求相关的
    -----assets文件夹:里面放置一些静态资源(一般共享的),放在aseets文件夹里面静态资源,在webpack打包的时候,会进行编译

    -----components文件夹:一般放置非路由组件获取全局组件

    -----icons这个文件夹的里面放置了一些svg矢量图

    -----layout文件夹:他里面放置一些组件与混入

    -----router文件夹:与路由相关的

    ----store文件夹:一定是与vuex先关的

    ----style文件夹:与样式先关的

    -----utils文件夹:request.js是axios二次封装文件

    -----views文件夹:里面放置的是路由组件

  • App.vue:根组件

  • main.js:入口文件

  • permission.js:与导航守卫相关

  • settings:项目配置项文件


二、项目配置

1.首先从github上下载简洁项目。

2.原始项目中并没有依赖环境,因此需要运行cmd,cnpm install 安装依赖环境。

  • 遇到报错: npm ERR! Cannot set [properties]

【Vue】项目:尚品汇后台管理系统(上)_第1张图片

  • 解决办法: 用cnpm安装

3.运行项目:查看package.json

package.json中关于运行的设置

  • 可知运行项目需要使用的语句是,npm run dev

  • 遇到报错:* core-js/modules/es. …

* core-js/modules/es. …报错

  • 解决办法:安装core-js cnpm core-js --save

4.后台管理系统API接口在线文档:

http://39.98.123.211:8170/swagger-ui.html

http://39.98.123.211:8216/swagger-ui.html


三、完成登录业务

1.找到登录业务所在位置,并修改文字。

2.书写API,将api user.js中的接口换成在线文档中真实的接口

【Vue】项目:尚品汇后台管理系统(上)_第2张图片

// api/user.js中
// 引入axios(axios进行二次封装)
import request from '@/utils/request'
// 对外暴露登录接口函数
export function login(data) {
  return request({
    url: '/admin/acl/index/login',
    method: 'post',
    data
  })
}
// 对外暴露获取用户信息的函数
export function getInfo(token) {
  return request({
    url: '/admin/acl/index/info',
    method: 'get',
    params: { token }
  })
}
// 对外暴露退出登录的函数
export function logout() {
  return request({
    url: '/admin/acl/index/logout',
    method: 'post'
  })
}
  1. axios二次封装:axios所在位置是utils/requerst.js中,由于原文件使用的是mock数据,返回的code如果成功的话是20000,但是如果是真实的接口,返回正确的code应该是200,因此修改代码。
 if (res.code !== 20000 && res.code !== 200)

4.换成真实接口之后需要解决代理跨域问题:webpack涉及的知识,可以去webpack官网-配置-devServer-devServer.proxy中寻找

   // vue.config.js
   // 配置代理跨域
   proxy: {
     '/dev-api': {
       target: 'http://39.98.123.211',
       pathRewrite: { '^/dev-api': '' }
     }
   }

四、退出登录业务

  1. 静态页面的更改:layout中包含系统的框架,比如菜单栏等,其中Navbar.vue中有退出等按钮,修改即可。

  2. 同时也要修改路由为真实路由。


五、项目路由的搭建

  1. 删除views中不需要的组件,只留下dashboard和login,同时删除相关路由。

  2. 创建project文件夹,里面存放管理系统需要的功能(tradeMark,Attr,Sku,Spu),分别创建vue文件。

  3. 模仿其他路由的书写方式,编写router中的index.js路由部分

{
  path: '/product',
  component: Layout,
  name: 'Product',
  meta: {
    title:'商品管理',icon:'el-icon-goods'
  },
  children: [
    {
      path: 'trademark',
      name: 'Trademark',
      component: () => import('@/views/product/tradeMark'),
      meta: {title:'品牌管理'}
    },
    {
      path: 'attr',
      name: 'Attr',
      component: () => import('@/views/product/Attr'),
      meta: {title:'品牌属性'}
    },
    {
      path: 'sku',
      name: 'Sku',
      component: () => import('@/views/product/Sku'),
      meta: {title:'Sku管理'}
    },
    {
      path: 'spu',
      name: 'Spu',
      component: () => import('@/views/product/Spu'),
      meta: {title:'Spu管理'}
    }
  ]
}
  1. 一个小错误:styles/index.scss中最后的一个类名写错了,导致一个布局不能实现,应为
.app-main {
padding: 20px;
}

六、完成品牌管理(tradeMark)静态组件

  1. 首先有一个添加按钮,并有一个加号图标

<el-button type="primary" icon="el-icon-plus" style="margin: 10px 0">添加el-button>
  1. 表格组件,表格中的数据的行数可以根据提供数组的长度来自动调整,因此我们只需要控制列数,有几列便有几个el-table-column。
<el-table style="width: 100%" border>
 <el-table-column prop="prop" label="序号" width="80px" align="center" type="index">
 el-table-column>
 <el-table-column prop="prop" label="品牌名称" width="width">
 el-table-column>
 <el-table-column prop="prop" label="品牌LOGO" width="width">
 el-table-column>
 <el-table-column prop="prop" label="操作" width="width">
 el-table-column>
el-table>
  • border:添加边框 ----整个表格是有边框的

  • label:显示的标题 ----每一列的表头是什么

  • width:对应列的宽度 ----第一列的宽度不一样,width的就是均分

  • align:对齐方式 左 中 右 ----序号列是居中对齐

  1. 分页器,之前封装过分页器,element-ui中也有封装好的分页器
<el-pagination
style="margin-top:20px;text-align:center"
  :current-page="1"
  :page-sizes="[3, 5, 10]"
  :page-size="3"
  :pager-count="7"
  layout="prev, pager, next, jumper,->,sizes,total"
  :total="99"
  @current-change="getPageList"
  @size-change="handleSizeChange"
  >
</el-pagination>
  • current-page当前第几页

  • total总页数

  • page-size每页数据数

  • page-sizes每页数据数备选

  • layout布局位置 — ->是居右

  • pager-count页面有的页码数,连续页码数为pager-count - 2 个


七、完成品牌管理列表的展示

  1. 数据获取,首先书写获取数据的请求,在线文档中有API.

    • 在api中创建product存放product相关请求(attr.js sku.js spu.js tradeMark.js).
// api/tradeMark.js中
import request from '@/utils/request'
export const reqTradeMarkList = (page, limit) => request({ url: `/admin/product/baseTrademark/${page}/${limit}`, method: 'get' });
  • 为了方便其他API的引入,可以把API挂载在原型上,就可以通过this.直接引入了。
// main.js中
// 引入相关API请求接口
import API from '@/api'
//将API挂载在原型上
// 任何组件可以使用API相关接口
Vue.prototype.$API = API
  • 由于这个接口需要两个参数,分别是page 和 limit,因此现在data中给出两个参数,以及给出一个数组,用于接收得到的数据。total是分析返回数据结构后也需要接收的。
data() {
  return {
    page:1,
    limit:3,
    total:0,
    list:[]
  }
  • 数据获取
mounted(){
  this.getPageList()
},
methods:{
  async getPageList(pager = 1){
    this.page = pager
    //解构参数
    const {page,limit} = this
    let result =  await this.$API.trademark.reqTradeMarkList(page,limit)
    if(result.code == 200){
      this.total = result.data.total
      this.list = result.data.records
    }
  },
}
  1. 数据展示

    • 数组的数据在el-table中用data表示,每一列的数据用el-table-column中的prop表示。

    • 如果表格中含有图片、按钮等其他结构,需要使用定义域插槽。定义域插槽的理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定

<el-table style="width: 100%" border :data="this.list">
  <el-table-column prop="prop" label="序号" width="80px" align="center" type="index">
  el-table-column>
  <el-table-column prop="tmName" label="品牌名称" width="width">
  el-table-column>
  <el-table-column prop="prop" label="品牌LOGO" width="width">
    <template slot-scope="{row,$index}">
      <img :src="row.logoUrl" alt="" style="width:100px;height:100px">
    template>
  el-table-column>
  <el-table-column prop="prop" label="操作" width="width">
    <template slot-scope="{row,$index}">
      <el-button icon="el-icon-edit" type="warning" size="mini">修改el-button>
      <el-button icon="el-icon-delete" type="danger" size="mini">删除el-button>
    template>
  el-table-column>
el-table>
  • 作用域插槽中的row表示el-table中含有的数据,已经按照行分配好的。
  1. 分页器点击的实现

    • 分页器还包括两个事件

      @current-change=“handleCurrentChange” ----点击页码的时候触发,会携带着点击的页码paper,可以和数据加载放在一起写,因为更换页码后也要重新请求。

      @size-change=“handleSizeChange” ----修改每页展示的数目,会携带这改变后的个数。

    • 分页器相关代码

<el-pagination
style="margin-top:20px;text-align:center"
  :current-page="page"
  :page-sizes="[3, 5, 10]"
  :page-size="3"
  :pager-count="7"
  layout="prev, pager, next, jumper,->,sizes,total"
  :total="total"
  @current-change="getPageList"
  @size-change="handleSizeChange"
  >
  
methods:{
  async getPageList(pager = 1){
    this.page = pager
    //解构参数
    const {page,limit} = this
    let result =  await this.$API.trademark.reqTradeMarkList(page,limit)
    if(result.code == 200){
      this.total = result.data.total
      this.list = result.data.records
    }
  },
  // 当分页器某一页需要展示数据条数发生变化时会触发
  handleSizeChange(limit){
    this.limit = limit
    this.getPageList()
  }
}

八、添加品牌与修改品牌的静态组件

  1. 点击添加或者修改会弹出相同的遮罩层,element-ui中有这个组件。图片上传也有相应的组件。

  2. 相关代码

<el-dialog title="添加品牌" :visible.sync="dialogFormVisible">
  <el-form style="width: 80%" :model="tmForm">
    <el-form-item label="品牌名称" label-width="100px">
      <el-input autocomplete="off" v-model="tmForm.tmName">el-input>
    el-form-item>
    <el-form-item label="品牌LOGO" label-width="100px">
      <el-upload
        class="avatar-uploader"
        action="dev-api/admin/product/fileUpload"
        :show-file-list="false"
        :on-success="handleAvatarSuccess"
        :before-upload="beforeAvatarUpload"
      >
        <img v-if="tmForm.logoUrl" :src="tmForm.logoUrl" class="avatar" />
        <i v-else class="el-icon-plus avatar-uploader-icon">i>
        <div slot="tip" class="el-upload__tip">
          只能上传jpg/png文件,且不超过500kb
        div>
      el-upload>
    el-form-item>
  el-form>
  <div slot="footer" class="dialog-footer">
    <el-button @click="dialogFormVisible = false">取 消el-button>
    <el-button type="primary" @click="addOrUpdateTradeMark">确 定el-button>
  div>
el-dialog>

九、完成添加品牌功能

  1. 添加品牌有自己的接口,且添加品牌和修改品牌的操作非常相似,区别就是修改品牌会返回id,因此,在API书写的时候,将添加品牌和修改品牌封装成一个请求。
export const reqAddOrUpdateTradeMark = (tradeMark) => { 
    if (tradeMark.id) {
        //修改品牌
        return request({ url: '/admin/product/baseTrademark/update', method: 'put', data: tradeMark })
    } else { 
        // 新增品牌
        return request({ url: '/admin/product/baseTrademark/save', method: 'post', data: tradeMark })
    }
}
  1. 为了对应请求参数,创建一个数据tmForm,包含logoUrl和tmName,用于参数传递。
data() {
  return {
    tmForm: {
      logoUrl: "",
      tmName: "",
    },
  };
},
  1. 添加品牌请求携带的参数是品牌名称和品牌图片,因此首先要收集到弹出框的信息,element-ui中用于收集表单元素的标签是 :model,可以告诉表单把数据收集到哪个元素身上。文字的收集采用v-model,图片的收集要使用action,可以设置图片的上传的地址。el-upload标签还有两个事件,:on-success:可以监测图片上次成功,会执行一次,:before-upload 可以在上传图片之前,会执行一次。

  2. 图片上传成功之前,要对图片进行格式大小判断,上传之后,要将图片url传递给tmForm中的logoUrl,展示的图片也要变成logoUrl。(见八的相关代码)

  3. 点击确认以后,首先隐藏弹出框,然后待着参数发请求,并弹出结果,重新获取数据。

// 点击添加
showDialog() {
  this.dialogFormVisible = true;
  // 每次点击添加之前都要清空数据,避免残留
  this.tmForm.logoUrl = ""
  this.tmForm.tmName = ""
},
//图片上传成功
handleAvatarSuccess(res, file) {
  this.tmForm.logoUrl = res.data;
},
//图片上传之前
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;
},
//添加或者修改品牌
async addOrUpdateTradeMark(){
  this.dialogFormVisible = false
  const result = await this.$API.trademark.reqAddOrUpdateTradeMark(this.tmForm)
  if(result.code == 200){
    //弹出成功信息
    this.$message({
      type:'success',
      message:this.tmForm.id?'修改品牌成功':'添加品牌成功'
    })
    this.getPageList(this.tmForm.id?this.page:1)
  }
}

十、完成品牌修改功能

  1. 点击修改后,弹出的框应该包含原来信息,但又不能直接拷贝,因为这样不管后续是否点击确定,都会修改了页面展示的信息,因此使用浅拷贝。
async updataTradeMark(row) {
  this.dialogFormVisible = true;
  // 不能直接修改tmForm,因为如果修改后取消了,但是页面已经展示出来了。因此,使用浅拷贝
  this.tmForm = {...row}
},
  1. 获取新的数据后,把row都作为参数传递给API,这样参数中就有了id,就可以调用更改的请求了,(代码见九的代码。)

十一、表单验证功能

1.添加和修改弹框中,应该对表单数据进行验证。包括品牌名称的长度以及是否上传了图片。
2.element-ui中包括表单验证的内容,分为表单验证和自定义规则验证。在Form表单模块中。
3.表单验证的使用:在需要验证的表单中加上:rules=“rules”,并在data()中书写rules规则

rules: {
  tmName: [
    // required是否必须校验   message提示信息   trigger用户行为设置  blur失焦  change文本变化
    { required: true, message: "请输入品牌名称", trigger: "blur" },
    {
      min: 2,
      max: 10,
      message: "长度在 2 到 10 个字符",
      trigger: "change",
    },
  ],
  logoUrl: [{ required: true, message: "请选择品牌的图片" }],
},

4.自定义规则验证:放弃了原本有的最大值最小值设定,改为自己在data中书写规则

var validateTMName = (rule, value, callback) => {
      if (value.length < 2 || value.length > 10) {
        callback(new Error("长度在 2 到 10 个字符"));
      } else {
        callback();
      }
    };
return {
  rules: {
    tmName: [
      // required是否必须校验   message提示信息   trigger用户行为设置  blur失焦  change文本变化
      { required: true, message: "请输入品牌名称", trigger: "blur" },
      // 自定义校验规则
      { validator: validateTMName, trigger: "blur" },
    ],
    logoUrl: [{ required: true, message: "请选择品牌的图片" }],
  },

十二、删除品牌的操作

1.点击删除以后,会弹出一个弹框,这个弹框在element-ui中是MessageBox 弹框,因此需要在点击删除品牌的回调函数中加入弹框相关代码,并修改文字。【Vue】项目:尚品汇后台管理系统(上)_第3张图片

2.如果点击的是弹框的确定,则进入请求阶段,API在线文档中包含删除品牌的请求,操作与之前的请求方式都是相同的。
3.需要注意的小点是,删除完毕后,如果当前列表的长度大于1,那么就返回当前页码的页面,如果小于1,则返回上一页。
4.相关代码(省略API的请求部分)

deleteTradeMark(row) {
  //MessageBox 弹框
  this.$confirm(`你确定删除${row.tmName}品牌吗?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(async() => {
      this.$message({
        type: "success",
        message: "删除成功!",
      });
      let result = await this.$API.trademark.reqDeletTradeMark(row.id)
      if(result.code === 200){
        this.getPageList(this.list.length>1?this.page:this.page-1)
      }
    })
    .catch(() => {
      this.$message({
        type: "info",
        message: "已取消删除",
      });
    });
},

你可能感兴趣的:(Vue项目,javascript,css3,vue.js,elementui)