功能模块

具体实现管理用户账号,即登录、退出、用户管理、权限管理等,商品管理,即商品分类、分类参数、商品信息,订单信息等以及数据统计。

前端技术栈

  1. Vue
  2. Vue Router
  3. Element-UI
  4. Axios
  5. Echarts

项目初始化

安装vue-cli脚手架

通过命令行全局安装vue-cli3.0以上版本脚手架。

npm install vue-cli -g

安装Vue Route路由

NPM

npm install vue-router

如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能:

import Vue from ‘vue’
import VueRouter from ‘vue-router’
Vue.use(VueRouter)

##安装Element-UI
NPM

npm i element-ui -S

安装Axios

NPM

npm install axios

安装Echarts

NPM

npm install echarts --save

注意
在用引用echarts的时候可能会出现一下错误
![]

报错原因
通过npm安装的是最新版echarts依赖,5.x版本,但是网上大部分教程的引入方式是按照4.x的方式引入,而5.x版本的引入方式有所改变,所以造成报错;

解决方案
将main.js中引入echarts的方式由4.x引入方式:

import echarts from ‘echarts’

换为5.x引入方式:

import * as echarts from ‘echarts’

项目结构

项目结构

  • node_modules:用于存放用包管理工具下载安装了的包
  • public:用于存放静态资源文件(不会经过webpack的打包处理)
  • src:用于存放项目的源码文件
    -assets:用于存放静态资源文件(会经过webpack的打包处理)
    • components:
    • plugins:用于存放element.js
    • router:用于存放路由文件
    • views:用于存放视图文件和Vue组件
    • APP.vue:项目入口文件
    • main-dev.js:开发模式项目入口文件
    • main-prodjs:上线打包,发布模式项目入口文件
  • editorconfig:代码规范配置文件
  • .gitignore:git忽略配置文件
  • babel.config.js:babel配置文件
  • package.json:项目配置文件
  • package-lock.json:项目包管控文件
  • README.md:说明文件
  • vue.config.js:配置文件

登录组件

  1. 设计思路:
    一个表单内包含一个显示用户名的文本输入框,和一个密码输入框,有登录按钮,和表单重置按钮。
  2. 功能需求分析:
    两个输入框,应当要有表单规则,用户名和密码必须要符合自定的验证规则,密码输出框需要隐藏密码,登录按钮被点击后通过路由跳转到home主页面,重置按钮被点击后要对表单清空重置。
  3. 具体实现;
    • 创建Login组件
      登录组件创建
    • 导入路由
import Login from "../views/Login";
{
    path: '/login',
    name: 'Login',
    component: Login
  },

template

 <el-form ref="loginFormRef" :model="loginForm" :rules="loginFormRules" label-width="0px" class="login_form">
        <!--        username-->
        <el-form-item prop="username">
          <!--    prop必须在<el-form-item>中      prop指定不同的验证规则 验证规则中的username-->
          <el-input v-model="loginForm.username" placeholder="用户名"
                    prefix-icon="el-icon-user-solid"></el-input>
        </el-form-item>

        <!--        password-->
        <el-form-item prop="password">
          <el-input v-model="loginForm.password"  placeholder="请输入密码"
                    prefix-icon="el-icon-s-goods" type="password" show-password></el-input>
        </el-form-item>

        <!--        按钮区-->
        <el-form-item class="btns">
          <el-button type="primary" :plain="true" @click="login">登录</el-button>
          <el-button type="info" @click="resetloginForm">重置</el-button>
        </el-form-item>
      </el-form>

:model=“loginForm”:是双向绑定的数据对象,loginForm包含username,password两个属性。
在data()中定义loginForm对象,loginForm对象使用来接收请求到的用户名和密码的

  //(1)登录表单的数据绑定对象
      loginForm:{
        username:'admin',
        password:'123456'
      },

** ref=“loginFormRef”**:是表单的引用对象,一般这个引用对象在表单中有两个用法
用法一
需要重置表单的时候可以用到loginFormRef引用对象。

		// resetFields()对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
      this.$refs.loginFormRef.resetFields();
      // this.$refs这个是<el-form ref="loginFormRef" 中自带的属性,
      // this.$refs获取表单的引用对象

用法二
提交表单的时候,需要表单预验证需要用到loginFormRef引用对象。

 this.$refs.loginFormRef.validate(async valid=>{
        // valid回调函数里面的Boolean值,实验的结果
        if(!valid)//  valid为false不发起请求,valid为true时才能发出请求
          return ;

**:rules=“loginFormRules”**表示给表单绑定一个规则。
在data()中对表单的规则作出具体规定


// (2) 表单的验证规则
      loginFormRules:{
        //  验证用户名是否合法,通过<el-form-item prop="username"> prop来绑定
        username:[
          { required: true, message: '请输入用户名称', trigger: 'blur' },
          { min: 3, max: 16, message: '用户名长度在 3 到 16 个字符', trigger: 'blur' }
        ],
        //  验证密码是否合法
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 6, max: 16, message: '密码长度在 6 到 16 个字符', trigger: 'blur' }
        ]
      }

prop=“username” prop属性是给定的文本输入框绑定值
**v-model=“loginForm.username” ** 这里是给用户名输入文本框绑定loginForm对象的username用户名。密码同上。

@click=“login” 登录按钮绑定点击事件。
在methods方法函数中定义login()函数,login点击事件的作用是 提交表单,跳转路由,提交表单就要表单预验证,验证通过那么向后端发起请求,const {data:res}=awai tthis.$http.post(‘Login’,this.loginForm)这里用到个Axios异步通信中的get方法,get方法需要的参数有请求的地址url和传递的参数this.loginForm。

值得注意的是get返回的是一个promise对象,而promise对象不是我们想要得到的数据,我们需用同async 和 await 来简化这次请求操作

请求的结果处理:
如果后端返回的状态码status !== 200 那么说明这次请求失败,提示用户错误信息,status== 200说明发起请求成功,也需要提示用户登录成功。

将登录成功之后token,保存到客户端的sessionStorage中
项目中除了登录页面之外的其他API接口,必须在登录之后才能访问,token只应在当前网站打开期间生效,所以将token 保存在sessionStorage 中

路由跳转
登录成功后需要通过路由来跳转到home页面

 login(){
      // 对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,
      // 并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise
      this.$refs.loginFormRef.validate(async valid=>{
        // valid回调函数里面的Boolean值,实验的结果
        if(!valid)//  valid为false不发起请求,valid为true时才能发出请求
          return ;
        //前后端交互
        //  结构放回一个promise  可以用await  和async
        const {data:res}= await this.$http.post('Login',this.loginForm)
        if(res.meta.status !==200)
           return this.$message.error(res.meta.msg);
        this.$message.success(res.meta.msg)

//  1.将登录成功之后token,保存到客户端的sessionStorage中
      //  1.1 项目中除了登录页面之外的其他API接口,必须在登录之后才能访问
        // 1.2 token只应在当前网站打开期间生效,所以将token 保存在sessionStorage 中
        window.sessionStorage.setItem("token",res.data.token);
        
// 2.通过编程式导航跳转到后台主页,路由地址是/home
          await this.$router.push("/home");


@click=“resetloginForm” 重置按钮绑定点击事件
清除内容表单。调用resetFields函数就行,这个是一个固定的方法。

 //  点击重置按钮,重置登录表单
    resetloginForm(){
      // console.log(this)
      // resetFields()对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
      this.$refs.loginFormRef.resetFields();
      // this.$refs这个是<el-form ref="loginFormRef" 中自带的属性,
      // this.$refs获取表单的引用对象
    },

script

<script>
export default {
  name: "Login",
  data() {
    return {
      //(1)登录表单的数据绑定对象
      loginForm:{
        username:'admin',
        password:'123456'
      },

      // (2) 表单的验证规则
      loginFormRules:{
        //  验证用户名是否合法,通过<el-form-item prop="username"> prop来绑定
        username:[
          { required: true, message: '请输入用户名称', trigger: 'blur' },
          { min: 3, max: 16, message: '用户名长度在 3 到 16 个字符', trigger: 'blur' }
        ],
        //  验证密码是否合法
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
          { min: 6, max: 16, message: '密码长度在 6 到 16 个字符', trigger: 'blur' }
        ]
      }
    }
  },
  methods:{
    //  点击重置按钮,重置登录表单
    resetloginForm(){
      // console.log(this)
      // resetFields()对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
      this.$refs.loginFormRef.resetFields();
      // this.$refs这个是<el-form ref="loginFormRef" 中自带的属性,
      // this.$refs获取表单的引用对象
    },

    //  登录预验证
    login(){
      // 对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,
      // 并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise
      this.$refs.loginFormRef.validate(async valid=>{
        // valid回调函数里面的Boolean值,实验的结果
        if(!valid)//  valid为false不发起请求,valid为true时才能发出请求
          return ;
        //前后端交互
        //  结构放回一个promise  可以用await  和async
        const {data:res}= await this.$http.post('Login',this.loginForm)
              // console.log(result);
        if(res.meta.status !==200)
           return this.$message.error(res.meta.msg);
        this.$message.success(res.meta.msg)
        // if(result.data.meta.status !==200) return  console.log('登录成功');
        // else  return  console.log('登录失败');

        // console.log(result.data);
      //  1.将登录成功之后token,保存到客户端的sessionStorage中
      //  1.1 项目中除了登录页面之外的其他API接口,必须在登录之后才能访问
        // 1.2 token只应在当前网站打开期间生效,所以将token 保存在sessionStorage 中
        window.sessionStorage.setItem("token",res.data.token);
      // 2.通过编程式导航跳转到后台主页,路由地址是/home
      //   await this.$router.push("/home/" + this.loginForm.username);
      //     this.loginForm.username传递的参数
          await this.$router.push("/home");

      });
    }
  }
}
</script>

CSS

<style scoped>
.login_container{
  background-image: url("../assets/loginback.jpg");
  height: 100%;
}

.login_box{
  /*display: flex;*/
  /*justify-content: center;*/
  /*align-items: center;*/
  width: 450px;
  height: 300px;
  background-image: url("../assets/1.jpg");
    background-size:100% 100% ;
  border-radius: 8px;
  position: absolute;
  top:50%;
  left: 50%;
  transform: translate(-50%,-50%);
}

/*.avatar_box{*/
/*  height:130px;*/
/*  width: 130px;*/
/*  border:1px solid #eee;*/
/*  border-radius: 50%;*/
/*  padding: 10px;*/
/*  box-shadow: 0 0 10px #fff;*/
/*  position: absolute;*/
/*  left: 50%;*/
/*  transform: translate(-50%,-50%);*/
/*  background-color: #fff;*/
/*}*/

/*.avatar_box img{*/
/*  width: 100%;*/
/*  height: 100%;*/
/*  border-radius: 50%;*/
/*  background-color: #eee;*/
/*}*/
.login_form{
  position: absolute;
  bottom: 0;
  width: 100%;
  padding: 0 20px;
  box-sizing: border-box;
}
.btns{
  display: flex;
  justify-content: center;
}


</style>


home组件

  1. 设计思路:
    home组件是整个项目最重要的部分,其他的功能模块都通过路由的渲染home组件上,home组件主要的实现的是对整个项目布局的设计,这里采用的是Element-UI中的Header、Aside、Main、Footer布局。

  2. 功能需求分析:
    Header可以选择放置项目的logo,用户头像等,Aside主要是侧边的绘制,Main主要是显示数据渲染的结果视图。

  3. 具体实现
    ** 导入路由**

import Home from "@/views/Home";
{
    path:'/home',
    props:true,  //获取name
    name:'Home',
 }

Header
头部区域以栅格布局的方式平均分为了三格,第三格设置了退出按钮,用户点击退出按钮后就触发logout事件,路由跳转到Login页面。

<el-header>
      <el-row>
        <el-col :span="4"><div class="grid-content bg-purple vcenter">西瓜心红</div></el-col>
        <el-col :span="12"><div class="grid-content bg-purple-light vcenter">我爱大西瓜</div></el-col>
        <el-col :span="8">
          <div class="grid-content bg-purple">

          <el-menu
              class="el-menu-demo"
              mode="horizontal"
              text-color="#545c64"
              active-text-color="#ffd04b">
            <el-submenu index="1" style="position: absolute;right:100px;" >
              <template slot="title"><i class="el-icon-user-solid"></i></template>
              <el-menu-item index="1-1">个人中心</el-menu-item>
              <el-menu-item index="1-2">安全中心</el-menu-item>
              <el-menu-item index="1-3">设置</el-menu-item>
              <el-menu-item index="1-4" @click="logout">退出</el-menu-item>
            </el-submenu>
          </el-menu>
          </div>
        </el-col>
      </el-row>
    </el-header>

login退出事件:
用户点击退出按钮后就触发logout事件,路由跳转到Login页面。

  //退出功能
    logout(){
      window.sessionStorage.clear();
      this.$router.push('/login')
    },

Aside–HTML
侧边导航栏。

<!--        侧边栏-->
      <el-aside :width="isCollpase?'64px':'300px'">
<!--          折叠与展开-->
          <div class="toggle_button" @click="toggleCollpase">|||</div>
          <el-menu
              class="el-menu-vertical-demo"
              background-color="#545c64"
              text-color="#fff"
              :unique-opened="true"
              :collapse="isCollpase"
              :collapse-transition="false"
              active-text-color="rgb(254,205,67)"
              router>
<!--            unique-opened	是否只保持一个子菜单的展开	boolean-->
            <el-submenu :index="item.id+''"
                        v-for="item in menuList"
                        :key="item.id">
              <template slot="title">
                <i :class="iconObject[item.id]"></i>
                <span>{{ item.authName }}</span>
              </template>
<!--         :index="'/'+subItem.path"绑定路由       -->
              <el-menu-item :index="'/'+subItem.path"
                            v-for="subItem in item.children"
                            :key="subItem.id">
                  <template>
                      <i :class="iconObject_children[subItem.id]"></i>
                      <span>{{ subItem.authName}}</span>
                  </template>
              </el-menu-item>
            </el-submenu>
          </el-menu>
      </el-aside>

toggleCollpase
折叠事件,“|||”被点击后会触发toggleCollpase这个事件,isCollpase是一个Boolean量默认为false, :width="isCollpase?‘64px’:‘300px’ isCollpase=true说明触发了toggleCollpase,导航栏需要折叠起来,折叠起来的导航栏的宽度为64px,isCollpase=false说明导航栏展开,宽度为300px。

//    折叠
        isCollpase:false,
//    折叠功能
      toggleCollpase(){
        this.isCollpase = !this.isCollpase
      },

menu参数说明

  • unique-opened 是否只保持一个子菜单的展开 boolean
  • collapse是否水平折叠收起菜单(仅在 mode 为 vertical 时可用)
  • collapse-transition 是否开启折叠动画
  • router,需要给menu绑定路由,不然不能通过路由来访问menu

一级
循环接收到的数组,然后把该数组的元素名称渲染到页面上

  • index参数使用用来绑定数据的,:index=“item.id+’’”,这里是绑定了menuList数组中元素的id,(这里的menuList数组是发起请求后端传过来的数组)通过id来绑定到数组元素。
  • 遍历数组:index=“item.id+’’” v-for=“item in menuList” :key=“item.id”
  • 各个元素的图标
  • {{ item.authName }} 各个数组元素的名称

二级菜单
循环 item.children

<!--         :index="'/'+subItem.path"绑定路由       -->
              <el-menu-item :index="'/'+subItem.path"
                            v-for="subItem in item.children"
                            :key="subItem.id">
                  <template>
                      <i :class="iconObject_children[subItem.id]"></i>
                      <span>{{ subItem.authName}}</span>
                  </template>
              </el-menu-item>

发起请求

//  获取所有导航栏的菜单
    async getMenuList(){
      const {data:res} = await this.$http.get('menus')
      //get是返回的是promise数据,为了简化promise操作 ,需要用async和await来简化
      if(res.meta.status !== 200) 
      	return this.$message.error(res.meta.msg)
      this.menuList = res.data
      // console.log(res)
    },

Aside–script

<script>
export default {
  name: "Home",
    //props:['name'],home页面接受到离着name数据
  data(){
    return {
      //  导航栏菜单数据
      menuList:[],
    //    处理导航栏一级菜单图标
        iconObject:{
            '125':'el-icon-user-solid',
            '103':'el-icon-s-cooperation',
            '101':'el-icon-shopping-bag-1',
            '102':'el-icon-tickets',
            '145':'el-icon-s-data'
        },

    //    导航栏二级菜单图标处理:
        iconObject_children:{
            '110':'el-icon-camera',
            '111':'el-icon-picture-outline-round',
            '104':'el-icon-s-promotion',
            '115':'el-icon-help',
            '121':'el-icon-s-platform',
            '112':'el-icon-s-home',
            '107':'el-icon-s-opportunity',
            '146':'el-icon-s-grid',
        },

    //    折叠
        isCollpase:false,

    //    被激活的链接地址
    //     activePath: ''
    }

  },
  //钩子函数
  created() {
    //页面刚要加载的时候就需要立即获取导航栏菜单
    this.getMenuList()

  //  赋值导航栏链接被激活状态
  //     this.activePath = window.sessionStorage.getItem('activePath')
  },
  methods:{
    //退出功能
    logout(){
      window.sessionStorage.clear();
      this.$router.push('/login')
    },
  //  获取所有导航栏的菜单
    async getMenuList(){
      const {data:res} = await this.$http.get('menus')
      //get是返回的是promise数据,为了简化promise操作 ,需要用async和await来简化
      if(res.meta.status !== 200) return this.$message.error(res.meta.msg)
      this.menuList = res.data
      // console.log(res)
    },
  //    折叠功能
      toggleCollpase(){
        this.isCollpase = !this.isCollpase
      },

  //    保存导航栏链接的激活状态
  //     saveNavState(activePath){
  //         // setItem向sessionStorage添加activePath
  //         window.sessionStorage.setItem('activePath',activePath)
  //         this.activePath = activePath
  //     }
  }
}

Aside–CSS

<style scoped>
.el-header, .el-footer {
  background-color: #B3C0D1;
  color: #333;
  text-align: center;
  line-height: 60px;
}

.el-aside {
  background-color: #545c64;
  color: #333;
  text-align: center;
  line-height: 10px;

}
.el-aside .el-menu{
    border-right: none;

}

.el-main {
  background-color: #E9EEF3;
  color: #333;
  text-align: center;
  line-height: 160px;
}

body > .el-container {
  margin-bottom: 40px;
}

.el-container:nth-child(5) .el-aside,
.el-container:nth-child(6) .el-aside {
  line-height: 260px;
}

.el-container:nth-child(7) .el-aside {
  line-height: 320px;
}

.container{
  height: 100%;
}

Welcome组件

  1. 设计思路:
    登录登录成功后就会直接跳转到这个欢迎页面,该页面不需要做什么内容,只要显示欢迎用户即可。
  2. 功能需求分析:
    显示欢迎用户界面。
  3. 具体实现:

创建组件
Welcome
Welcome组件内容
这里我是插入一张图片

 <div class="welcome">
        <img src="../../assets/welcome.png" alt="">
    </div>

导入路由

import Welcome from "../views/Welcome/Welcome";
 //路由重定向
    redirect:'/welcome',
    component: Home,

用户列表

  1. 设计思路;
    一个头部面包屑,搜索文本框,添加用户按钮,一个表格显示用户信息,可编辑用户信息,分配角色和删除用户信息。
  2. 功能需求分析:
    面包屑点击某个选项可跳转到相应的页面,文本输入框可以通过用户名来查询现有的用户的信息,添加用户,填写用户的基本信息后提交到服务器,刷新表格,编辑就是对用户信息进行修改,删除将对应的用户从表格中删除。
  3. 具体实现:
    导入路由
    值得注意的是User组件应该添加到home的子路由中
import User from "../views/user/User";
children:[
      {
        path:'/welcome',
        name:'Welcome',
        component:Welcome
      },
      {
        path:'/users',
        name:'User',
        component: User

      },

User–template

面包屑

<!--    面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
          <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
          <el-breadcrumb-item>用户管理</el-breadcrumb-item>
          <el-breadcrumb-item>用户列表</el-breadcrumb-item>
        </el-breadcrumb>

卡片区el-card开始
显示用户信息,添加用户,删除用户,编辑用户信息等

  • 搜索区
    • el-input 属性及其参数梳理
      • v-model="queryInfo.query"绑定搜索的值
        
      • clearable 搜索内容可点击X号删除
      • @clear=“getUserList” 搜索完成后清除输入文本框内容
      • @click=“addDialogVisible = true” 添加用户按钮的点击事件,被点击后显示添加用户对话框
<el-row :gutter="10">
                <el-col :span="8">
                    <el-input placeholder="请输入内容"
                              v-model="queryInfo.query"
                              clearable
                              @clear="getUserList">
<!--                        clearable带X号,可清空按钮,@clear="getUserList",点击后触发clear清空事件,-->
<!--                        恢复原来的用户列表数据-->
                        <el-button slot="append" icon="el-icon-search"
                        @click="getUserList"></el-button>
<!--                        @click="getUserList"查询的是用户列表中的数据,所以绑定获取用户列表数据getUserList()就可以了;-->
                    </el-input>
                </el-col>
                <el-col :span="4" style="width: 0;">
                    <el-button type="success" @click="addDialogVisible = true">添加用户</el-button>
                </el-col>
            </el-row>
  • 用户列表区
    • el-table表格参数及其属性梳理
      • :data=“userlist” 绑定表格数据数组
      • border 边框
      • stripe 隔行显色
    • el-table-column 参数及其属性梳理
      • label=“姓名” 表头
      • prop=“username” 绑定的具体数据
    • 定义域插槽的用法
      • 定义
        template slot-scope=“scope” /template
      • {{scope.row}}可以拿到状态的数据 scope.row这行所对应的数据
      • v-model="scope.row.mg_state"获取并绑定了用户状态
      • scope.row.id 获取到了这一行的id值
      <!--        用户列表区-->
            <el-table :data="userlist" border stripe>
<!--                索引列-->
                <el-table-column label="#"    type="index"></el-table-column>
                <el-table-column label="姓名" prop="username"></el-table-column>
                <el-table-column label="邮箱" prop="email"></el-table-column>
                <el-table-column label="电话" prop="mobile"></el-table-column>
                <el-table-column label="角色" prop="role_name"></el-table-column>
                <el-table-column label="状态">
<!--                    作用域插槽slot-scope="scope"-->
                    <template slot-scope="scope">
<!--                        {{scope.row}}可以拿到状态的数据-->
<!--                        scope.row这行所对应的数据-->
<!--                        v-model="scope.row.mg_state"获取到了后端的状态-->
                        <el-switch
                            v-model="scope.row.mg_state"
                            active-color="#13ce66"
                            inactive-color="#ff4949"
                            @change="userStateChanged(scope.row)">
                        </el-switch>
                    </template>
                </el-table-column>
                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-row>
<!--                            修改按钮-->
                            <el-tooltip  effect="dark" content="修改" placement="top-start" :enterable="false">
                                <el-button type="primary"
                                           size="min"
                                           icon="el-icon-edit"
                                           circle
                                            @click="showEditDialog(scope.row.id)"></el-button>
                            </el-tooltip>

                            <el-tooltip  effect="dark" content="删除" placement="top-end" :enterable="false">
                                <el-button type="danger" size="min" icon="el-icon-delete" circle
                                @click="removeUserById(scope.row.id)"></el-button>
                            </el-tooltip>

                            <el-tooltip effect="dark" content="分配角色" placement="top" :enterable="false">
                                <el-button type="success" size="min" icon="el-icon-s-tools" circle  @click="setRole(scope.row)"></el-button>
                            </el-tooltip>
                        </el-row>
                    </template>
                </el-table-column>
            </el-table>

  • 分页区
    • el-pagination 参数及其属性梳理
      • @size-change=“handleSizeChange” pageSize 改变时会触发
      • @current-change=“handleCurrentChange” currentPage 改变时会触发
      • :current-page=“queryInfo.pagenum” 当前页数
      • :page-sizes="[2,5,10,15]" 页面显示的信息条数
      • :page-size=“queryInfo.pagesize” 每页显示个数选择器的选项设置
      • :total=“total” 总条数
<!--            分页区域-->
            <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current-page="queryInfo.pagenum"
                :page-sizes="[2,5,10,15]"
                :page-size="queryInfo.pagesize"
                layout="total, sizes, prev, pager, next, jumper"
                :total="total">
            </el-pagination>

卡片区结束

添加用户对话框

  • el-dialog对话框参数及其属性梳理
    • :visible.sync=“addDialogVisible” 决定对话框是否显示
    • @close=“addDialogClose” 对话框关闭之后,需要清空对话框中表单的信息。
  • el-form 表单参数及其属性梳理
    • :model="addForm 表单绑定的数据
      
    • :rules=“addFormRules” 表单的规则对象
    • ref=“addFormRef” l表单的引用对象,
      两个地方用到了表单引用对象,表单重置和提交数据预验证
  • el-button 参数及其属性梳理
    • " @click=“addUser” 添加用户确定按钮,绑定点击事件,点击事件触发后需要提交用户信息,然后再显示新添加的用户信息显示到表格中。
<!--        添加用户对话框-->
        <el-dialog
            title="添加用户"
            :visible.sync="addDialogVisible"
            width="50%"
            @close="addDialogClose">
<!--            内容主体区-->
<!--          ref="addFromRef"是表单的引用对象  表单的重置需要用到 -->
            <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="用户名" prop="username">
                    <el-input v-model="addForm.username"></el-input>
                </el-form-item>

                <el-form-item label="密码" prop="password">
                    <el-input v-model="addForm.password"></el-input>
                </el-form-item>

                <el-form-item label="邮箱" prop="email">
                    <el-input v-model="addForm.email"></el-input>
                </el-form-item>

                <el-form-item label="手机" prop="mobile">
                    <el-input v-model="addForm.mobile"></el-input>
                </el-form-item>
            </el-form>
<!--            底部区域-->
            <span slot="footer" class="dialog-footer">
                <el-button @click="addDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="addUser">确 定</el-button>
            </span>

        </el-dialog>

在这里插入图片描述

编辑对话框

<!--     修改按钮弹出 修改用户对话框-->
        <el-dialog
            title="修改信息"
            :visible.sync="editDialogVisible"
            width="50%"
            @close="editDialogClose">
<!--            //    展示编辑修改对话框-->
<!--            修改用户的主体部分-->
            <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="用户名" prop="username">
                    <el-input v-model="editForm.username" disabled></el-input>
                </el-form-item>
                <el-form-item label="邮箱" prop="email">
                    <el-input v-model="editForm.email"></el-input>
                </el-form-item>
                <el-form-item label="手机" prop="mobile">
                    <el-input v-model="editForm.mobile"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="editUser">确 定</el-button>
            </span>
        </el-dialog>

在这里插入图片描述

分配角色对话框

  • el-select 参数及其属性梳理
    • v-model=“selectRoleId” 绑定选择的值
    • v-for=“item in rolesList” 循环用户数组
    • :label="item.roleName"选择分配角色的名称
    • :value=“item.id” 选项的值绑定给选择的选项的id
<!--        分配角色对话框-->
        <el-dialog
            title="分配角色"
            :visible.sync="setRoleDialogVisible"
            width="50%"
            @close="setRoleDialogClose"
            class="dailog_role">
            <div>
                <p class="user">当前的用户:{{userInfo.username}}</p>
                <p class="user">当前的角色:{{userInfo.role_name}}</p>
                <P class="user">分配角色:
                    <el-select v-model="selectRoleId" placeholder="请选择">
                        <el-option
                            v-for="item in rolesList"
                            :key="item.id"
                            :label="item.roleName"
                            :value="item.id">
                        </el-option>
                    </el-select>
                </P>
            </div>
            <span slot="footer" class="dialog-footer">
                <el-button @click="setRoleDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="saveRoleInfo">确 定</el-button>
            </span>
        </el-dialog>

在这里插入图片描述

User页面整体效果
在这里插入图片描述

User–script

数据分析

  • 邮箱验证和手机验证的方法
//  邮箱验证
      var checkEmail = (rule,value,callback)=>{
      //  验证邮箱的正则表达式
          const regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/
          if(regEmail.test(value)){
          //    合法的邮箱
              callback()
          }
          callback(new Error('请输入合法的邮箱'))
      }

    //  手机号验证规则
      var checkMobile = (rule,value,callback)=>{
      //      手机正则表达式
          const regMobile = /^(0|86|17951)?(13[0-9]|15[0123456789]|17[678]|18[0-9]|14[57])[0-9]{8}$/
          if(regMobile.test(value)){
          //    合法手机号
              callback()
          }
          callback(new Error('请输入合法的手机号'))
      }
  • queryInfo 获取用户列表的参数
  • userlist:[],用户裂变数据
  • total: 0, 用户列表的总条数
  • addForm 添加用户的表单数据
  • addFormRules 添加表单的验证规则对象
  • editForm 编辑用户的表单数据
  • editFormRules
  • userInfo 需要被分配角色的用户信息
  • rolesList 所有角色的数据列表
  • selectRoleId 已选中的角色Id值

函数分析

  • getUserList() 获取用户列表数据,发起请求
    • this.userlist = res.data.users 获取用户信息,保存在userlist 数组中
    • this.total = res.data.total 获取到总条数,保存到total中
  • handleSizeChange(newSize) 分页事件 监听pageSize改变的事件
    • his.queryInfo.pagesize = newSize;
    • this.getUserList(); 重新获取数据,像后端发起请求
  • handleCurrentChange(newSize) 监听页码值改变的事件
    • his.queryInfo.pagenum= newSize;
    • this.getUserList(); 重新获取数据,像后端发起请求
  • userStateChanged(userInfo) 监听switch按钮状态的变化
    • users/${userInfo.id}/state/${userInfo.mg_state}
    • 特别注意的是:ulr中要是带有参数 这个时候不能用单引号,应该使用反引号来写路径,反引号:在英文输入法的情况下,连续按两下 Esc 下面的键 ``
  • addUser( )点击确定按钮添加用户
    • this.$refs.addFormRef.validate 表单预验证
    • this.$http.post(‘users’,this.addForm) 发起请求
  • removeUserById(id) 删除事件
    • this.$confirm 提示 弹框
    • this.$http.delete(‘users/’+id)发起请求
  • setRole(userInfo) 发起请求获取到角色信息
    -saveRoleInfo()分配角色,添加到列表中
<script>
export default {
  name: "User",
    props:['name'],
  data(){
    //  自定义验证规则(手机号和邮箱)
    //  邮箱验证
      var checkEmail = (rule,value,callback)=>{
      //  验证邮箱的正则表达式
          const regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/
          if(regEmail.test(value)){
          //    合法的邮箱
              callback()
          }
          callback(new Error('请输入合法的邮箱'))
      }

    //  手机号验证规则
      var checkMobile = (rule,value,callback)=>{
      //      手机正则表达式
          const regMobile = /^(0|86|17951)?(13[0-9]|15[0123456789]|17[678]|18[0-9]|14[57])[0-9]{8}$/
          if(regMobile.test(value)){
          //    合法手机号
              callback()
          }
          callback(new Error('请输入合法的手机号'))
      }
    return{
        // 以下是接受后端数据用的数据,
        // 获取用户列表的参数
        queryInfo:{
            //查询参数
            query: '',

            //  当前页码n
            pagenum: 1,
            //  当前每页显示条数
            pagesize:5
        },
        //用户列表数据
        userlist:[],
        //总数据条数
        total: 0,
    //    控制添加用户对话框的显示与隐藏
        addDialogVisible:false,
        //添加用户的表单数据
        addForm:{
            username:'',
            password: '',
            email: '',
            mobile:''
        },
        //添加表单的验证规则对象
        addFormRules:{
            username:[
                {required:true,message:'请输入用户名',trigger:'blur'},
                {min:3 , max:10 , message: '用户名的长度在3~10个字符之间',trigger: 'blur'}
            ],
            password:[
                {required:true,message:'请输入密码',trigger:'blur'},
                {min:6 , max:15, message: '密码的长度在6~15个字符之间',trigger: 'blur'}
            ],
            email:[
                {required:true,message:'请输入邮箱',trigger:'blur'},
                {validator:checkEmail,trigger: 'blur'}
            ],
            mobile:[
                {require:true,message:'请输入手机号',trigger:'blur'},
                {validator:checkMobile,trigger: 'blur'}
            //    自定义校验规则的使用{validator:checkMobile,trigger: 'blur'}
            ]
        },


        //修改用户对话框的显示与隐藏
        editDialogVisible : false,
        editForm:{},
        editFormRules:{
            email:[
                {required:true,message:'请输入邮箱',trigger:'blur'},
                {validator:checkEmail,trigger: 'blur'}
            ],
            mobile:[
                {required:true,message:'请输入手机号',trigger:'blur'},
                {validator:checkMobile,trigger: 'blur'}
                //    自定义校验规则的使用{validator:checkMobile,trigger: 'blur'}
            ]
        },

    //    分配角色
        setRoleDialogVisible:false,
        //需要被分配角色的用户信息
        userInfo: {},
        //所有角色的数据列表
        rolesList:[],
        // 已选中的角色Id值
        selectRoleId:''
    }
  },
    created() {
      this.getUserList()
    },
    methods: {
        //    获取用户列表数据
        async getUserList() {
            //get参数,params来接收传过来的数据
            const {data: res} = await this.$http.get('users',
                {params: this.queryInfo})
            if (res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            //获取用户信息
            this.userlist = res.data.users
            //获取总条数
            this.total = res.data.total

        },
    //    分页事件
    //    监听pageSize改变的事件
        handleSizeChange(newSize) {
            // console.log(newSize)
            this.queryInfo.pagesize = newSize;
            //重新获取数据,像后端发起请求
            this.getUserList();
        },
        //监听页码值改变的事件
        handleCurrentChange(newSize) {
            // console.log(newNum);
            this.queryInfo.pagenum = newSize;
            //重新获取数据
            this.getUserList();
        },

    //    监听switch按钮状态的变化
        async userStateChanged(userInfo){
            // console.log(userInfo)
        //    调用API接口保存状态量
        //    特别注意的是:ulr中要是带有参数 这个时候不能用单引号,应该使用反引号来写路径,反引号:在英文输入法的情况下,连续按两下 Esc 下面的键 ``
            const {data:res} =await this.$http.put(
                `users/${userInfo.id}/state/${userInfo.mg_state}`
            )
           if(res.meta.status !== 200){
               //更新用户状态失败,并没有修改数据库中的状态,而是改变了页面的变化,这个时候需要把用户状态还原
               userInfo.mg_state  = !userInfo.mg_state
               return this.$message.error(res.meta.msg)
           }
           this.$message.success(res.meta.msg)
        },

    //    监听添加用户对话框的关闭事件  添加用户对话框表单重置
        addDialogClose(){
            // 通过引用ref调用resetFields()
            this.$refs.addFormRef.resetFields();
        },

        //点击确定按钮添加用户  预校验
        addUser(){
            this.$refs.addFormRef.validate(async valid=>{
                if(!valid){
                    //校验失败
                    return
                }
            //    预校验通过可以发发起添加用户的网络请求
              const {data:res} =await this.$http.post('users',this.addForm)
                if(res.meta.status !==201){
                    //获取添加用户失败
                    this.$message.error(res.meta.msg)
                }

            //    添加用户成功
                this.$message.success(res.meta.msg)
            //   添加成功之后, 隐藏添加用户的对话框
                this.addDialogVisible = false

            //    刷新用户列表  重新获取用户列表数据
                await this.getUserList()
            })
        },


        //修改用户信息
        //修改按钮点击事件  显示与隐藏修改对话框
        //点击修改按钮时,显示对话框并且对话框内要有用户的基本信息
        //通过id来查询用户的信息
        async showEditDialog(id){

            const {data:res}= await this.$http.get('users/'+id)
            if(res.meta.status !==200){
                return this.$message.error(res.meta.msg)
            }
            this.editForm = res.data
            this.editDialogVisible =true
        },

        //修改用户信息对话框的,表单重置
        editDialogClose(){
            // 通过引用ref调用resetFields()
            this.$refs.editFormRef.resetFields()
        },

         //修改用户信息
         editUser(){
            //修改表单预验证
            this.$refs.editFormRef.validate( async valid =>{
                if(!valid){
                    return
                }
            //    发起修改信息的网络请求
                const {data:res} = await this.$http.put('users/'+this.editForm.id,{email:this.editForm.email,mobile:this.editForm.mobile})
                if(res.meta.status !==200){
                    return this.$message.error(res.meta.msg)
                }

                //   添加成功之后, 隐藏添加用户的对话框
                this.editDialogVisible = false
                //    刷新用户列表  重新获取用户列表数据
                await this.getUserList()
                this.$message.success(res.meta.msg)
            })
        },

    //    通过Id来删除用户信息
        async removeUserById(id){
            const confirmResult = await this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(err => err)

        //    如果用户确认删除,则返回值为字符串confirm
        //    如果用户点击取消,则返回值为cancel
        //     console.log(confirmResult)
            if(confirmResult !== 'confirm'){
                return this.$message.info('取消删除')
            }
            // console.log('确认删除')
            const {data:res } = await  this.$http.delete('users/'+id)
            if(res.meta.status !== 200){
                return  this.$message.error(res.meta.msg)
            }
        //    删除成功
            this.$message.success(res.meta.msg)
            await this.getUserList()
        },

    //    分配角色
        async setRole(userInfo){
           this.userInfo = userInfo

        //    在展示对话框之前,获取所有角色的列表
            const  {data:res} = await this.$http.get('roles')
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            this.rolesList = res.data
            this.setRoleDialogVisible = true
        },

    //    点击分配角色 确定按钮触发的事件 分配角色
        async saveRoleInfo(){
            if(!this.selectRoleId){
                return this.$message.error('请选择要分配的角色')
            }

        //    选择新角色,发起请求
            const {data:res} = await this.$http.put(`users/${this.userInfo.id}/role`,{
                rid:this.selectRoleId
            })
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }

            this.$message.success(res.meta.msg)
            await this.getUserList()
            this.setRoleDialogVisible = false

        },

    //监听分配对话框的关闭事件,关闭对话框后,选择的角色清空,用户也清空
        setRoleDialogClose(){
            this.selectRoleId = '',
                this.rolesList = []
        }

    }
}
</script>

User–CSS

<style scoped>
.user{
    line-height: 100%;
    margin-top: 20px;
    display: flex;
    justify-content: left;
}
.dailog_role{
    line-height: 100%;
    padding: 0;
}

权限列表

面包屑

<!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>权限管理</el-breadcrumb-item>
            <el-breadcrumb-item>权限列表</el-breadcrumb-item>
        </el-breadcrumb>

卡片视图区
主要功能是显示用户权限

  • 权限等级这列用到了作用域插槽
    v-if=“scope.row.level ==0” 判断权限的等级,level 为0 则是一级,为1是二级,为2是三级
  <el-tag type="success" v-if="scope.row.level ==0">一级</el-tag>
   <el-tag type="info" v-else-if="scope.row.level ==1">二级</el-tag>
       <el-card>
<!--            权限表单区域-->
            <el-table
                :data="rightsList"
                border
                stripe>
                <el-table-column
                    label="#"
                    type="index">
                </el-table-column>

                <el-table-column
                    prop="authName"
                    label="权限名称">
                </el-table-column>

                <el-table-column
                    prop="path"
                    label="路径">
                </el-table-column>

                <el-table-column
                    label="权限等级">
                    <template slot-scope="scope">
                        <el-tag type="success" v-if="scope.row.level ==0">一级</el-tag>
                        <el-tag type="info" v-else-if="scope.row.level ==1">二级</el-tag>
                        <el-tag type="warning" v-else>三级</el-tag>
                    </template>
                </el-table-column>
            </el-table>

        </el-card>

获取到的权限数据列表存放在rightsList数组中

data(){
        return{
            rightsList:[],
        }
    },

getRightsList函数是获取权限数据,发起get请求,获取到的数据保存在rightsList 数组中,然后就可以通过数组将数据渲染到页面了。

 methods:{
        async getRightsList(){
            // rights/:type
            const {data:res} = await this.$http.get('rights/list')
            if(res.meta.status !==200){
                return  this.$message.error(res.meta.msg)
            }
            this.rightsList = res.data
            // this.$message.success(res.meta.msg)
        }
    }

在这里插入图片描述

角色列表

面包屑

 <!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>权限管理</el-breadcrumb-item>
            <el-breadcrumb-item>权限列表</el-breadcrumb-item>
        </el-breadcrumb>

添加角色按钮
点击添加按钮显示添加角色对话框

<!--            添加角色按钮-->
            <el-row>
                <el-col>
                    <el-button type="success" @click="addRoleDialogVisible = true">添加角色</el-button>
                </el-col>
            </el-row>
  • 添加角色对话框
 <el-dialog
            title="添加角色提示"
            :visible.sync="addRoleDialogVisible"
            width="50%"
            @close="addDialogClose">
            <el-form :model="addRoleForm"
                     :rules="addRoleFormRules"
                     ref="addRoleFormRef"
                     label-width="100px">
                <el-form-item label="角色名称" prop="roleName">
                    <el-input v-model="addRoleForm.roleName"></el-input>
                </el-form-item>
                <el-form-item label="角色描述" prop="roleDesc">
                    <el-input v-model="addRoleForm.roleDesc"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="addRoleDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="addRoles">确 定</el-button>
            </span>
        </el-dialog>
  • addRoleDialogVisible是一个Boolean值,用来确定对话框是否显示

    addRoleDialogVisible : false,

  • @close="addDialogClose"关闭对话框触发的事件,将添加对话框的内容清空,确保下次打开的对话框是空白的。
//关闭对话框后,重置清空对话框
        addDialogClose(){
            this.$refs.addRoleFormRef.resetFields()
        },
  • :model=“addRoleForm” 绑定表单数据的对象

addRoleForm:{},

  • :rules=“addRoleFormRules” 表单的验证规则

addRoleFormRules:{
roleName: [
{ required: true, message: ‘请输入角色名称’, trigger: ‘blur’ },
{min:2 , max:10 , message: ‘角色名称的长度在3~10个字符之间’,trigger: ‘blur’}
],
roleDesc:[
{required:true,message:‘请输入角色描述’,trigger:‘blur’},
{min:2, max:10 , message: ‘角色描述的长度在3~10个字符之间’,trigger: ‘blur’}
]
},

  • ref=“addRoleFormRef” 表单的引用对象,两次用到,预验证表单和清空表单

//表单预验证
this.$refs.addRoleFormRef.validate(async valid=> {
if(!valid){
return
}

//关闭对话框后,重置清空对话框
addDialogClose(){
this.$refs.addRoleFormRef.resetFields()
},

  • prop=“roleName” 绑定数据具体属性
  • 效果展示
    在这里插入图片描述

展开列

<!--                展开列-->
                <el-table-column
                    type="expand">
                    <template slot-scope="scope">
<!--                            {{scope.row}}-->
                        <el-row v-for="(item1 , index1) in scope.row.children"
                                :key="item1.id"
                                :class="['bdbottom',index1===0?'bdtop':'','vcenter']">
<!--                            一级权限-->
                            <el-col :span="5">
<!--                                {{item1.authName}}-->
                                <el-tag closable
                                        @close="removeRightsById(scope.row,item1.id)">{{item1.authName}}</el-tag>
                                <i class="el-icon-caret-right"></i>
                            </el-col>

<!--                            渲染二级和三级权限-->
                            <el-col :span="19">
<!--                                通过for循环嵌套 渲染二级权限-->
                                <el-row v-for="(item2,index2) in item1.children"
                                        :key="item2.id"
                                        :class="['bdbottom',index2===0?'bdtop':'','vcenter']">
<!--                                    二级权限-->
                                    <el-col :span="6">
                                        <el-tag type="success"
                                                closable
                                                @close="removeRightsById(scope.row,item2.id)">{{item2.authName}}</el-tag>
                                        <i class="el-icon-caret-right"></i>
                                    </el-col>

<!--                                    三级权限-->
                                    <el-col :span="18">
                                        <el-tag type="danger"
                                                v-for="(item3 , index3) in item2.children"
                                                :key="item3.id"
                                                closable
                                                @close="removeRightsById(scope.row,item3.id)">
                                            {{item3.authName}}
                                        </el-tag>


                                    </el-col>
                                </el-row>
                            </el-col>
                        </el-row>
                    </template>
                </el-table-column>
  • v-for="(item1 , index1) in scope.row.children" 循环编列该行的children,因为children内包含了角色的权限,需要渲染到页面上。
  • 一级权限
    • closable表示可删除
    • @close="removeRightsById(scope.row,item1.id)"通过id来删除权限
      注意这里用到了MessageBox弹框,需要在main.js添加

    import { MessageBox } from ‘element-ui’

 <el-col :span="5">
            <el-tag closable
                 	@close="removeRightsById(scope.row,item1.id)">{{item1.authName}}</el-tag>
             <i class="el-icon-caret-right"></i>
</el-col>

removeRightsById(scope.row,item1.id)函数,确定删除,发起delete请求,参数(roles/${role.id}/rights/${rightId}),role.id表示该角色的ID,rightId表示该权限的ID

//根据Id删除对应的权限
        async removeRightsById(role,rightId){
        //    弹框提示用户是否删除该权限
            const confirmResult = await this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(err => err)
            if(confirmResult !== "confirm"){
                return this.$message.info('取消删除')
            }

            //发起删除请求
            const {data : res} = await this.$http.delete(`roles/${role.id}/rights/${rightId}`)
            if(res.meta.status !==200){
                return this.$message.error(res.meta.msg)
            }
            this.$message.success(res.meta.msg)
            // await this.getRolesList()
            role.children = res.data
        },
  • 二级和三级权限
    • v-for="(item2,index2) in item1.children" 遍历一级权限的 children。就可以得到二级权限和三级权限
    • 二级权限,同样调用removeRightsById(scope.row,item2.id)函数对权限进行删除操作
        二级权限-->
      <el-col :span="6">
           <el-tag type="success"
                   closable
                    @close="removeRightsById(scope.row,item2.id)">{{item2.authName}}</el-tag>
           <i class="el-icon-caret-right"></i>
      </el-col>
    
    • 三级权限
    • v-for="(item3 , index3) in item2.children"遍历二级权限的children得到三级权限
    • @close="removeRightsById(scope.row,item3.id)"调用删除权限函数
         三级权限-->
       <el-col :span="18">
          <el-tag type="danger"
                  v-for="(item3 , index3) in item2.children"
                 :key="item3.id"
                  closable
                  @close="removeRightsById(scope.row,item3.id)">
                   {{item3.authName}}
           </el-tag>
       </el-col>
    

操作列

<el-table-column
     label="操作">
  <template slot-scope="scope">
     <el-button type="success" icon="el-icon-edit" @click="showEditDialog(scope.row.id)">编辑</el-button>
     <el-button type="warning" icon="el-icon-share" @click="removeRolesDialog(scope.row.id)">删除</el-button>
      <el-button type="danger" icon="el-icon-delete" @click="showSetRightDialog(scope.row)">分配权限</el-button>
  </template>
 </el-table-column>
  • 编辑按钮
    • 编辑对话框
    • v-model="editRoleForm.roleName"和 v-model="editRoleForm.roleDesc"双向绑定要修改的数据
    <el-dialog
            title="编辑角色"
            :visible.sync="editDialogVisible"
            width="50%"
            @close="editDialogClose">
            <el-form :model="editRoleForm"
                     :rules="editRoleFormRules"
                     ref="editRoleFormRef"
                     label-width="100px">
                <el-form-item label="角色名称" prop="roleName">
                    <el-input v-model="editRoleForm.roleName" disabled></el-input>
                </el-form-item>
                <el-form-item label="角色描述" prop="roleDesc">
                    <el-input v-model="editRoleForm.roleDesc"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="editRoles">确 定</el-button>
            </span>
        </el-dialog>
    
    
    • @click="showEditDialog(scope.row.id)"编辑按钮的点击事件,显示编辑对话框内容,传递参数scope.row.id该行的ID,通过get请求来获取该行的数据,获取到的数据保存到editRoleForm 对象中,方便渲染到表单上。
     //    编辑角色按钮
        async showEditDialog(id){
                //编辑角色请求
                const {data:res} = await this.$http.get('roles/'+id)
                if(res.meta.status !== 200){
                    return this.$message.error(res.meta.msg)
                }
                //编辑成功
                this.editRoleForm = res.data
                this.editDialogVisible = true
                // this.$message.success(res.meta.msg)
            },
    
    • @click="editRoles"点击编辑对话框的确定按钮触发的事件,将修改好的数据提交到数据库中,在更新用户列表内容。
    //    编辑对话框确定按钮,点击提交表单
        editRoles(){
            //表单预验证
            this.$refs.editRoleFormRef.validate( async valid =>{
                if(!valid){
                    return
                }
                //编辑,发起网络请求
                const {data:res} = await this.$http.put('roles/'+this.editRoleForm.roleId,{roleName:this.editRoleForm.roleName,roleDesc:this.editRoleForm.roleDesc})
                if(res.meta.status !== 200){
                    return this.$message.error(res.meta.msg)
                }
                //请求成功
                this.editDialogVisible = false
                await this.getRolesList()
                this.$message.success(res.meta.msg)
    
            })
    
        },
    

在这里插入图片描述

  • 删除按钮
    • @click="removeRolesDialog(scope.row.id)"绑定点击事件。删除按钮被点击后,该用户被删除。
    async removeRolesDialog(id){
            const rolesResult = await this.$confirm('此操作将永久删除该角色, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(err => err)
            //    如果用户确认删除,则返回值为字符串confirm
            //    如果用户点击取消,则返回值为cancel
            if(rolesResult !== 'confirm'){
                return  this.$message.info('取消删除操作')
            }
        //    删除请求
            const {data : res} = await this.$http.delete('roles/'+id)
            if(res.meta.status !== 200){
                return  this.$message.error(res.meta.msg)
            }
            await this.getRolesList()
            this.$message.success(res.meta.msg)
        },
    
  • 分配权限
    • 分配权限对话框
      • 注意:
        这里用到了Tree 树形控件
      • :data=“rightList” 绑定数据
      • show-checkbox 节点是否可被选择
      • :props=“treeProps” 配置选项

      // 树形控件的属性绑定对象
      treeProps:{
      label:‘authName’,
      children:‘children’
      },

      • node-key="id"每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
      • default-expand-all 是否默认展开所有节点
      • :default-checked-keys=“defaultKeys” 默认展开的节点的 key 的数组

      //默认选中的节点的Id值的数组
      defaultKeys:[],

     <el-dialog
            title="分配权限"
            :visible.sync="setRightDialogVisible"
            width="50%"
            @close="showSetRightDialogClose">
            <el-tree
                :data="rightList"
                show-checkbox
                :props="treeProps"
                node-key="id"
                default-expand-all
                :default-checked-keys="defaultKeys"
                ref="treeRightRef">
            </el-tree>
            <span slot="footer" class="dialog-footer">
                <el-button @click="setRightDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="allotRight">确 定</el-button>
            </span>
        </el-dialog>
    
    • showSetRightDialog(role)展示分配权限
      this.roleId = role.id 将角色的ID保存起来,在分配权限的时候还要用到
    // 展示分配权限
        async showSetRightDialog(role){
            //当前权限的id
            this.roleId = role.id
            // 请求获取所有权限数据
            const {data:res} = await this.$http.get('rights/tree')
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
    
            this.rightList = res.data
    
            //获取三级节点的id
            this.getLeaKeys(role,this.defaultKeys)
            this.setRightDialogVisible = true
        },	
    
    • @click="allotRight"确定给角色分配权限
    //    点击确定按钮,通过Id来分配权限
        async allotRight(){
            //获取半选和全选下的id
            const keys = [
                ...this.$refs.treeRightRef.getCheckedKeys(),
                ...this.$refs.treeRightRef.getHalfCheckedKeys()
            ]
            // console.log(keys)
            const  keysStr = keys.join(',')
    
        //    发起请求
            const  {data:res} = await this.$http.post(`roles/${this.roleId}/rights`,
                {rids:keysStr})
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            this.$message.success(res.meta.msg)
            await this.getRolesList()
            this.setRightDialogVisible = false
        }
    
    

在这里插入图片描述

商品列表

面包屑

<!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>商品分类</el-breadcrumb-item>
        </el-breadcrumb>

添加商品按钮

  <el-row>
<!--                搜索框-->
                <el-col :span="6">
                    <el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="searchGoods">
                        <el-button slot="append" icon="el-icon-search" @click="searchGoods"></el-button>
                    </el-input>
                </el-col>
<!--              添加商品按钮  -->
                <el-col :span="18">
<!--                    通过路由跳转到添加商品页面-->
                    <el-button type="success" class="btn" @click="goAddGoods">添加商品</el-button>
                </el-col>
            </el-row>
  • v-model=“queryInfo.query”

// 获取商品列表的参数
queryInfo: {
query:’’,
pagenum:1,
pagesize:10
},

  • searchGoods事件

//搜索框的按钮点击事件
searchGoods(){
this.getGoodsList()
},

  • 添加按钮 @click=“goAddGoods”,跳转到add页面
//    添加商品事件
        goAddGoods(){
        //    通过路由跳转到添加商品页面
            this.$router.push('/goods/add')

        },
  • add组件

    • 注册步骤
      • :active=“activeIndex-0” 注册步骤索引,
    <!--            注册步骤区域-->
            <el-steps :space="200" :active="activeIndex-0" finish-status="success" class="line-height " align-center>
                <el-step title="基本参数"></el-step>
                <el-step title="商品参数"></el-step>
                <el-step title="商品属性"></el-step>
                <el-step title="商品照片"></el-step>
                <el-step title="商品内容"></el-step>
                <el-step title="完成"></el-step>
            </el-steps>
    
    • table标签页
      • v-model=“activeIndex” 数据联动效果
      • :before-leave=“beforeTabsLeave” 切换标签之前的钩子,若返回 false 或者返回 Promise 且被 reject,则阻止切换。

      //处理阻止Tabs标签切换的函数
      beforeTabsLeave(activeName,oldActiveName){
      if(oldActiveName === ‘0’ && this.addForm.goods_cat.length !== 3){
      this.$message.error(‘请选择三级商品分类’)
      return false
      }
      // 可以跳转
      },

      • @tab-click=“tabsClick” tab-click tab 被选中时触发
      //被选中的标签 tab 实例
      async tabsClick(){
          // console.log(this.activeIndex)
          //选中的是动态参数面板
          if(this.activeIndex === '1'){
          //    获取商品参数
              const {data:res} = await this.$http.get(`categories/${this.getCateId}/attributes`,{
                  params: { sel: 'many' }
              })
              if(res.meta.status !== 200){
                  return this.$message.error(res.meta.msg)
              }
          //遍历 res.data 中attr_vals,将res.data 中attr_vals 字符变为数组
              res.data.forEach(item => {
                  item.attr_vals=item.attr_vals.length===0?[]:item.attr_vals.split(' ')
              })
          //    获取商品参数成功,并保存到manyCateList数组中
              this.manyTableData = res.data
          }else if(this.activeIndex === '2'){
          //    商品属性列表
              const {data:res} = await this.$http.get(`categories/${this.getCateId}/attributes`,{
                  params:{sel:'only'}
              })
              if(res.meta.status !== 200){
                  return  this.$message.error(res.meta.msg)
              }
              this.onlyTableData = res.data
              console.log(this.onlyTableData)
          }
      },
      
    <!--            table栏区域-->
            <el-form :model="addForm" :rules="addFormRule" ref="addFormRef" label-width="100px" label-position="top" class="from_items">
                <el-tabs v-model="activeIndex" :tab-position="`left`" style="height: 100%;"
                    :before-leave="beforeTabsLeave" @tab-click="tabsClick">
    <!--              v-model="activeIndex"  数据联动效果-->
    					 .........................
    		</el-tabs>
    </  <el-form>
    
    • 商品参数
      • v-for="item in manyTableData"循环获取到的动态参数
     <el-tab-pane label="商品参数">
                        <el-form-item  :label="item.attr_name"  v-for="item in manyTableData" :key="item.attr_id" border>
                            <el-checkbox-group v-model="item.attr_vals">
                                <el-checkbox :label="itemParams" v-for="(itemParams,index) in item.attr_vals" :key="index" border></el-checkbox>
                            </el-checkbox-group>
                        </el-form-item>
      </el-tab-pane>
    
    • 商品图片
      • action 必选参数,上传的地址
      • :on-preview="handlePreview"点击文件列表中已上传的文件时的钩子
      • :on-remove=“handleRemove” 删除图片时的钩子
      • :on-success="handleSuccess"文件上传成功时的钩子

      //处理图片预览操作
      handlePreview(file){
      // file将预览的图片信息
      console.log(file)
      // 获取图片的临时路径
      this. previewPath= file.response.data.url
      // 弹出对话框, 放大图片,
      this.imageDialogVisible = true
      },
      // 处理图片删除操作
      handleRemove(file){
      // file将要被移除的图片信息
      // 1.获取图片的临时路径
      // console.log(file)
      const filePath = file.response.data.tmp_path
      // 从pics数组中,查找到这个图片对应的索引,findIndex这个函数可以查找到数组的索引
      // x表示数组中的每一项
      const i = this.addForm.pics.findIndex(x=>x.pic === filePath)
      // 调用数组中的splice函数来删除该图片
      this.addForm.pics.splice(i,1)
      // console.log(this.addForm)
      },
      //图片上传成功之后会触发的事件,事件会返回图片的临时保存地址,我们需要接收这个地址
      handleSuccess(response){
      // 获取到一个图片的对象。
      const picsInfo = {pic:response.data.tmp_path}
      // 将获取到的图片地址,保存添加到pics数组中
      this.addForm.pics.push(picsInfo)
      },

    <el-tab-pane label="商品照片">
                        <el-upload
                            :action="actionURL"
                            :on-preview="handlePreview"
                            :on-remove="handleRemove"
                            list-type="picture"
                            :headers="headersImg"
                            :on-success="handleSuccess">
                            <el-button size="small" type="primary">点击上传</el-button>
                        </el-upload>
    </el-tab-pane>
        <!--                        :on-success="handleSuccess"文件上传成功时的钩子-->
    

    在这里插入图片描述

    • add组件源码
<template>
    <div>
        <!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>商品列表</el-breadcrumb-item>
        </el-breadcrumb>

<!--        卡片视图区-->
        <el-card>
<!--           提示区 -->
            <el-alert
                title="消息提示的文案"
                type="info"
                show-icon
                center
                class="line-height">
            </el-alert>
<!--            注册步骤区域-->
            <el-steps :space="200" :active="activeIndex-0" finish-status="success" class="line-height " align-center>
<!--                active是注册步骤激活项的索引-->
                <el-step title="基本参数"></el-step>
                <el-step title="商品参数"></el-step>
                <el-step title="商品属性"></el-step>
                <el-step title="商品照片"></el-step>
                <el-step title="商品内容"></el-step>
                <el-step title="完成"></el-step>
            </el-steps>


<!--            table栏区域-->
            <el-form :model="addForm" :rules="addFormRule" ref="addFormRef" label-width="100px" label-position="top" class="from_items">
                <el-tabs v-model="activeIndex" :tab-position="`left`" style="height: 100%;"
                    :before-leave="beforeTabsLeave" @tab-click="tabsClick">
    <!--              v-model="activeIndex"  数据联动效果-->
                    <el-tab-pane label="基本信息">
                        <el-form-item label="商品名称" prop="goods_name">
                            <el-input v-model="addForm.goods_name"></el-input>
                        </el-form-item>
                        <el-form-item label="价格" prop="goods_price">
                            <el-input v-model="addForm.goods_price" type="number"></el-input>
                        </el-form-item>
                        <el-form-item label="数量" prop="goods_number">
                            <el-input v-model="addForm.goods_number" type="number"></el-input>
                        </el-form-item>
                        <el-form-item label="重量" prop="goods_weight" type="number">
                            <el-input v-model="addForm.goods_weight"></el-input>
                        </el-form-item>
<!--                        商品分类级联选择器-->
                        <el-form-item  label="商品分类" prop="goods_cat">
                            <el-cascader
                                expand-trigger="hover"
                                v-model="addForm.goods_cat"
                                :options="cateList"
                                :props="cateParams"
                                @change="handleChange">
                            </el-cascader>
                        </el-form-item>

                    </el-tab-pane>
                    <el-tab-pane label="商品参数">
<!--                        遍历参数-->
                        <el-form-item  :label="item.attr_name"  v-for="item in manyTableData" :key="item.attr_id" border>
                            <el-checkbox-group v-model="item.attr_vals">
                                <el-checkbox :label="itemParams" v-for="(itemParams,index) in item.attr_vals" :key="index" border></el-checkbox>
                            </el-checkbox-group>
                        </el-form-item>
                    </el-tab-pane>
                    <el-tab-pane label="商品属性">
                        <el-form-item :label="item.attr_name" v-for="item in onlyTableData" :key="item.attr_id" >
                            <el-input v-model="item.attr_vals"  ></el-input>
                        </el-form-item>
                    </el-tab-pane>
                    <el-tab-pane label="商品照片">
<!--                        :on-success="handleSuccess"文件上传成功时的钩子-->
                        <el-upload
                            :action="actionURL"
                            :on-preview="handlePreview"
                            :on-remove="handleRemove"
                            list-type="picture"
                            :headers="headersImg"
                            :on-success="handleSuccess">
                            <el-button size="small" type="primary">点击上传</el-button>
                        </el-upload>
                    </el-tab-pane>
                    <el-tab-pane label="商品内容">
                        <quill-editor v-model="addForm.goods_introduce">
                        </quill-editor>
                        <el-button type="primary" style="margin-top: 20px" @click="addGoods">添加商品</el-button>
                    </el-tab-pane>
                </el-tabs>
            </el-form>
        </el-card>

<!--图片预览对话框-->
        <el-dialog
            title="图片预览"
            :visible.sync="imageDialogVisible"
            width="70%"
            @close="imageHandleClose">
            <img :src="previewPath" alt="" style="width: 100%;">
        </el-dialog>
    </div>
</template>

<script>
// <!--导入lodash-->
import _ from 'lodash'
export default {
    name: "Add",
    data(){
        return{
            // 注册步骤的的索引标识
            activeIndex : '0',
            //添加商品的表单数据对象
            addForm:{
                goods_name:'',
                goods_price:0,
                goods_number:0,
                goods_weight:0,
                // 级联选择框双向绑定到的数组
                goods_cat:[],
                //图片上传都保存到pics数组中
                pics:[],
                //商品内容
                goods_introduce:'',
                //商品的参数(数组),包含 `动态参数` 和 `静态属性`
                attrs:[]
            },
            addFormRule:{
                goods_name:[
                    {required:true,message:'请输入商品名',trigger:'blur'},
                ],
                goods_price:[
                    {required:true,message:'请输入商品价格',trigger:'blur'},
                ],
                goods_number:[
                    {required:true,message:'请输入商品数量',trigger:'blur'},
                ],
                goods_weight:[
                    {required:true,message:'请输入商品重量',trigger:'blur'},
                ],
                goods_cat:[
                    {required:true,message:'请选择商品分类',trigger:'blur'},
                ]
            },
            // 获取到的商品分类列表的数据数组
            cateList:[],
        // //    级联选择框的配置对象
            cateParams:{
                label:'cat_name',
                value:'cat_id',
                children:'children'
            },
            //获取到的商品的动态属性,保存在manyCateList数组中
            manyTableData:[],
            //获取到的商品的动态属性,保存在onlyTableData对象中
            onlyTableData:[],
            //上传图片的目标地址
            actionURL:'http://127.0.0.1:8888/api/private/v1/upload',
            //设置上传的请求头部headersImg
            headersImg: {
                Authorization:window.sessionStorage.getItem('token')
            },
            //图片预览
            imageDialogVisible:false,
            //获取到的图片的保存路径url
            previewPath:'',


        }
    },
    created() {
        this.getCateList()
    },
    methods:{
        //获取商品分类数据
        async getCateList(){
            const {data:res} = await this.$http.get('categories')
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }

            this.cateList = res.data;
        },

        //    级联选择框选中项变化会触发这个函数
        handleChange(){
            //只能选择三级分类
            if(this.addForm.goods_cat.length !== 3){
                this.addForm.goods_cat = []
            }
        },
        //处理阻止Tabs标签切换的函数
        beforeTabsLeave(activeName,oldActiveName){
            if(oldActiveName === '0' && this.addForm.goods_cat.length !== 3){
                this.$message.error('请选择三级商品分类')
                return false
            }

            // 可以跳转
        },
        //被选中的标签 tab 实例
        async tabsClick(){
            // console.log(this.activeIndex)
            //选中的是动态参数面板
            if(this.activeIndex === '1'){
            //    获取商品参数
                const {data:res} = await this.$http.get(`categories/${this.getCateId}/attributes`,{
                    params: { sel: 'many' }
                })
                if(res.meta.status !== 200){
                    return this.$message.error(res.meta.msg)
                }
            //遍历 res.data 中attr_vals,将res.data 中attr_vals 字符变为数组
                res.data.forEach(item => {
                    item.attr_vals=item.attr_vals.length===0?[]:item.attr_vals.split(' ')
                })
            //    获取商品参数成功,并保存到manyCateList数组中
                this.manyTableData = res.data
            }else if(this.activeIndex === '2'){
            //    商品属性列表
                const {data:res} = await this.$http.get(`categories/${this.getCateId}/attributes`,{
                    params:{sel:'only'}
                })
                if(res.meta.status !== 200){
                    return  this.$message.error(res.meta.msg)
                }
                this.onlyTableData = res.data
                console.log(this.onlyTableData)
            }
        },
        //处理图片预览操作
        handlePreview(file){
            // file将预览的图片信息
            console.log(file)
        //    获取图片的临时路径
            this. previewPath= file.response.data.url
        //   弹出对话框, 放大图片,
            this.imageDialogVisible = true
        },
        // 处理图片删除操作
        handleRemove(file){
            // file将要被移除的图片信息
        //    1.获取图片的临时路径
        //     console.log(file)
            const filePath = file.response.data.tmp_path
        //    从pics数组中,查找到这个图片对应的索引,findIndex这个函数可以查找到数组的索引
            // x表示数组中的每一项
            const i = this.addForm.pics.findIndex(x=>x.pic === filePath)
        //    调用数组中的splice函数来删除该图片
            this.addForm.pics.splice(i,1)
            // console.log(this.addForm)
        },
        //图片上传成功之后会触发的事件,事件会返回图片的临时保存地址,我们需要接收这个地址
        handleSuccess(response){
        //    获取到一个图片的对象。
            const  picsInfo = {pic:response.data.tmp_path}
        //    将获取到的图片地址,保存添加到pics数组中
            this.addForm.pics.push(picsInfo)
        },
        //关闭预览对话框
        imageHandleClose(){
            this.imageDialogVisible = false
        },

        //编辑好商品的所有属性和参数后,提交到服务器上
        addGoods(){
            //表单预验证
            this.$refs.addFormRef.validate(async  valid=>{
                if (!valid){
                    return this.$message.error('请填写完整的商品参数')
                }
                //将goods_cat分割为字符串
                //安装lodash插件中的cloneDeep方法用来深拷贝goods_cat
                const  form = _.cloneDeep(this.addForm)
                form.goods_cat = form.goods_cat.join(',')
                //处理动态参数
                this.manyTableData.forEach( item=>{
                    const newInfo ={
                        attr_id:item.attr_id,
                        attr_value:item.attr_vals.join(' ')
                    }
                    this.addForm.attrs.push(newInfo)
                })
                //处理静态属性
                this.onlyTableData.forEach(item=>{
                    const newInfo = {
                        attr_id:item.attr_id,
                        attr_value:item.attr_vals
                    }
                    this.addForm.attrs.push(newInfo)
                })
                //将addForm对象的attrs数组,赋值给form对象
                form.attrs = this.addForm.attrs

                //发起请求添加商品
                const {data:res} = await this.$http.post('goods',form)
                if(res.meta.status !== 201){
                    return this.$message.error(res.meta.msg)
                }
                this.$message.success(res.meta.msg)
                //添加商品完成后,路由跳转到商品列表页面
                await this.$router.push('/goods')
            })

        }
    },
    computed:{
        //获取三级分类的索引函数
        getCateId(){
            if(this.addForm.goods_cat.length === 3 ){
                //三级分类的索引
                return this.addForm.goods_cat[2]
            }
            return  null
        }
    }
}
</script>

<style scoped>
.from_items{
    text-align: left;
    line-height: 100%;
}
.el-checkbox{
    margin:0 10px 0 0  !important; ;
}

</style>
  • 商品列表对时间的处理
 <el-table-column
                    label="创建时间"
                    prop="add_time"
                    width="200px">
                    <template slot-scope="scope">
<!--                        时间格式 通过时间过滤器来装换-->
                        {{scope.row.add_time | dataFormat}}
                    </template>
                </el-table-column>

在main.js入口文件中添加时间过滤器

//格式化时间,时间过滤器
Vue.filter('dataFormat', function (originVal) {
    const dt = new Date(originVal)

    const y = dt.getFullYear()
    const m = (dt.getMonth() + 1 + '').padStart(2, '0')
    const d = (dt.getDate() + '').padStart(2, '0')

    const hh = (dt.getHours() + '').padStart(2, '0')
    const mm = (dt.getMinutes() + '').padStart(2, '0')
    const ss = (dt.getSeconds() + '').padStart(2, '0')
    // yyyy-mm-dd hh:mm:ss
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})

在这里插入图片描述

  • 商品列表源码
<template xmlns:el-col="http://www.w3.org/1999/html">
    <div>
        <!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>商品列表</el-breadcrumb-item>
        </el-breadcrumb>

<!--        卡片区-->
        <el-card >
<!--            搜索区-->
            <el-row>
<!--                搜索框-->
                <el-col :span="6">
                    <el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="searchGoods">
                        <el-button slot="append" icon="el-icon-search" @click="searchGoods"></el-button>
                    </el-input>
                </el-col>
<!--              添加商品按钮  -->
                <el-col :span="18">
<!--                    通过路由跳转到添加商品页面-->
                    <el-button type="success" class="btn" @click="goAddGoods">添加商品</el-button>
                </el-col>
            </el-row>

<!--            表格区-->
            <el-table
                :data="goodsList"
                border
                stripe>
                <el-table-column
                    type="index"
                    label="#">
                </el-table-column>
                <el-table-column
                    prop="goods_name"
                    label="商品名称">
                </el-table-column>

                <el-table-column
                    prop="goods_price"
                    label="商品价格"
                    width="100px">
                </el-table-column>

                <el-table-column
                    prop="goods_weight"
                    label="商品重量"
                    width="70px">
                </el-table-column>

                <el-table-column
                    prop="goods_number"
                    label="商品数量"
                    width="70px">
                </el-table-column>

                <el-table-column
                    label="创建时间"
                    prop="add_time"
                    width="200px">
                    <template slot-scope="scope">
<!--                        时间格式 通过时间过滤器来装换-->
                        {{scope.row.add_time | dataFormat}}
                    </template>
                </el-table-column>

                <el-table-column
                    label="操作"
                    width="300px">
                    <template slot-scope="scope">
                        <el-button type="success" icon="el-icon-edit" size="min" @click="showEditGoodsById(scope.row.goods_id)">编辑</el-button>
                        <el-button type="danger" icon="el-icon-delete" size="min" @click="removeGoodsById(scope.row.goods_id)">删除</el-button>
                    </template>
                </el-table-column>
            </el-table>

<!--            分页区-->
            <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current-page="queryInfo.pagesize"
                :page-sizes="[1, 5, 10, 20,40]"
                :page-size="queryInfo.pagesize"
                layout="total, sizes, prev, pager, next, jumper"
                :total="total">
            </el-pagination>
        </el-card>

<!--        编辑对话框-->
        <el-dialog
            title="添加商品"
            :visible.sync="editGoodsDialogVisible"
            width="50%"
            @close="editGoodsDialogClose">
            <el-form :model="editGoodsForm" :rules="editGoodsFormRules" ref="editGoodsFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="商品名称" prop="goods_name">
                    <el-input v-model="editGoodsForm.goods_name"></el-input>
                </el-form-item>

                <el-form-item label="商品价格" prop="goods_price">
                    <el-input v-model="editGoodsForm.goods_price"></el-input>
                </el-form-item>

                <el-form-item label="商品数量" prop="goods_number">
                    <el-input v-model="editGoodsForm.goods_number"></el-input>
                </el-form-item>

                <el-form-item label="商品重量" prop="goods_weight">
                    <el-input v-model="editGoodsForm.goods_weight"></el-input>
                </el-form-item>

            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editGoodsDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="editGoods">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

<script>
export default {
    name: "List",
    data(){
        return{
        //  商品列表数据
            goodsList:[],
            // 获取商品列表的参数
            queryInfo: {
                query:'',
                pagenum:1,
                pagesize:10
            },
            //    总数据条数
            total : 0,
            //编辑
            editGoodsDialogVisible:false,
            //编辑对话框的数据对象
            editGoodsForm:{
                goods_name:'',
                goods_price:0,
                goods_number:0,
                goods_weight:0

            },
            //商品的Id
            goodsId:0,
            editGoodsFormRules:{
                goods_name:[
                    {required:true,message:'请输入商品名',trigger:'blur'}
                ],
                goods_price:[
                    {required:true,message:'请输入商品价格',trigger:'blur'}
                ],
                goods_number:[
                    {required:true,message:'请输入商品数量',trigger:'blur'}
                ],
                goods_weight:[
                    {required:true,message:'请输入商品重量',trigger:'blur'}
                ]
            }



        }
    },
    created() {
        this.getGoodsList()
    },
    methods:{
        //商品列表数据
        async getGoodsList(){
            const {data:res} = await this.$http.get('goods',{params:this.queryInfo})
            if (res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            // console.log(res)
            this.goodsList = res.data.goods
            this.total = res.data.total
        },

        //分页事件操作
        handleSizeChange(newSize){
            this.queryInfo.pagesize = newSize
            this.getGoodsList()
        },
        handleCurrentChange(newNum){
            this.queryInfo.pagesize = newNum
            this.getGoodsList()
        },

        //搜索框的按钮点击事件
        searchGoods(){
          this.getGoodsList()

        },

    //    添加商品事件
        goAddGoods(){
        //    通过路由跳转到添加商品页面
            this.$router.push('/goods/add')

        },
        addGoodsDialogClose(){
            //关闭添加商品对话框,则情况输入框内的数据
            this.$refs.addGoodsFormRef.resetFields()
        },

        //删除操作
        async removeGoodsById(goodId){
            const confirmResult = await this.$confirm('此操作将永久删除该商品, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(err => err)
            //    如果用户确认删除,则返回值为字符串confirm
            //    如果用户点击取消,则返回值为cancel
            if(confirmResult !== 'confirm'){
                return this.$message.info('取消删除')
            }

        //    确定删除
            const  {data:res} = await this.$http.delete('goods/'+goodId)
                if (res.meta.status !==200){
                    return  this.$message.error(res.meta.msg)
                }
            this.$message.success(res.meta.msg)
            await this.getGoodsList()

        },
        //编辑按钮事件
        async showEditGoodsById(id){
            this.goodsId = id
            const {data:res} = await this.$http.get('goods/'+this.goodsId)
            if(res.meta.status !== 201){
                return this.$message.error(res.meta.msg)
            }
          this.editGoodsDialogVisible =true
        },
        editGoods(){
            // 表单预验证
            this.$refs.editGoodsFormRef.validate( async valid=>{
                if(!valid){
                    return
                }
            //    发起请求
                const {data:res} = await this.$http.put('goods/'+this.goodsId, {params:this.editGoodsForm})
                if(res.meta.status !== 201){
                    return this.$message.error(res.meta.msg)
                }
                this.editGoodsDialogVisible = false
                await this.getGoodsList()
                this.$message.success(res.meta.msg)
            })
        },
        editGoodsDialogClose(){
            this.$refs.editGoodsFormRef.resetFields()
        }

    }
}
</script>

<style scoped>
.btn{
    display: flex;
    justify-content: left;
}
</style>

分类参数

  • 级联选择器
<!--            级联框选择-->
            <el-row class="goods_opt">
                <el-col class="goods_col">
                    <span>选择商品分类:</span>
<!--                    级联选择器-->
                    <el-cascader
                        expand-trigger="hover"
                        :options="paramsList"
                        :props="goodsParams"
                        v-model="selecteParamsKeys"
                        @change="handleChange">
                    </el-cascader>
                </el-col>
            </el-row>

  • options 可选项数据源,键名可通过 Props 属性配置

  • :props="goodsParams"配置选项

    // 级联选择框的配置对象
    goodsParams:{
    value:‘cat_id’,
    label:‘cat_name’,
    children:‘children’
    },

    • v-model=“selecteParamsKeys” 绑定选择的参数
    • @change=“handleChange” 当选中节点变化时触发

    // 级联选择框选中项变化会触发这个函数
    handleChange(){
    this.getParamsDataList()
    },

    在这里插入图片描述

    • 分类参数源码
<template>
    <div>
        <!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>参数列表</el-breadcrumb-item>
        </el-breadcrumb>

        <!--        卡片视图区-->
        <el-card>
<!--            头部警告区-->
            <el-alert
                title="注意:只允许为第三级分类设置相关参数"
                type="warning"
                show-icon
                :closable="false">
            </el-alert>

<!--            级联框选择-->
            <el-row class="goods_opt">
                <el-col class="goods_col">
                    <span>选择商品分类:</span>
<!--                    级联选择器-->
                    <el-cascader
                        expand-trigger="hover"
                        :options="paramsList"
                        :props="goodsParams"
                        v-model="selecteParamsKeys"
                        @change="handleChange">
                    </el-cascader>
                </el-col>
            </el-row>

<!--            标签页区-->
            <el-tabs v-model="activeName" @tab-click="handleTabClick">
<!--                添加动态参数-->
                <el-tab-pane label="动态参数" name="many">
                    <el-button class="btns" type="success" :disabled="isBtnDisabled" @click="addDialogVisible = true">添加参数</el-button>
<!--                    动态参数表格-->
                    <el-table :data="manyTableData" border stripe>
<!--                        展开列-->
                        <el-table-column type="expand" >
                            <template slot-scope="scope">
                                <el-tag v-for="(item,index) in scope.row.attr_vals"
                                        :key="index"
                                        closable
                                        @close="handleChanged(index,scope.row)">
                                    {{item}}
                                </el-tag>

<!--                                循环渲染tag标签-->
                                <el-input
                                    class="input-new-tag"
                                    v-if="scope.row.inputVisible"
                                    v-model="scope.row.inputValue"
                                    ref="saveTagInput"
                                    size="small"
                                    @keyup.enter.native="handleInputConfirm(scope.row)"
                                    @blur="handleInputConfirm(scope.row)"
                                >
                                </el-input>
<!--                                添加按钮-->
                                <el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button>
                            </template>

                        </el-table-column>
                        <el-table-column type="index" label="#"></el-table-column>
                        <el-table-column label="参数名称" prop="attr_name"></el-table-column>
                        <el-table-column label="操作">
                            <template slot-scope="scope">
                                <el-button type="primary" icon="el-icon-edit" size="min" @click="showEditDialog(scope.row.attr_id)">修改</el-button>
                                <el-button type="danger" icon="el-icon-delete" size="min" @click="removeParams(scope.row.attr_id)">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>

<!--                添加静态属性-->
                <el-tab-pane label="静态属性" name="only">
                    <el-button class="btns" type="success" :disabled="isBtnDisabled" @click="addDialogVisible = true">添加属性</el-button>
<!--                    静态属性表格-->
                    <el-table :data="onlyTableData" border stripe>
                        <el-table-column type="expand" >
                            <template slot-scope="scope">
                                <el-tag v-for="(item,index) in scope.row.attr_vals"
                                        :key="index"
                                        closable
                                        @close="handleChanged(index,scope.row)">
                                    {{item}}
                                </el-tag>

                                <!--                                循环渲染tag标签-->
                                <el-input
                                    class="input-new-tag"
                                    v-if="scope.row.inputVisible"
                                    v-model="scope.row.inputValue"
                                    ref="saveTagInput"
                                    size="small"
                                    @keyup.enter.native="handleInputConfirm(scope.row)"
                                    @blur="handleInputConfirm(scope.row)"
                                >
                                </el-input>
                                <!--                                添加按钮-->
                                <el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button>
                            </template>
                        </el-table-column>
                        <el-table-column type="index" label="#"></el-table-column>
                        <el-table-column label="属性名称" prop="attr_name"></el-table-column>
                        <el-table-column label="操作">
                            <template slot-scope="scope">
                                <el-button type="primary" icon="el-icon-edit" size="min" @click="showEditDialog(scope.row.attr_id)">修改</el-button>
                                <el-button type="danger" icon="el-icon-delete" size="min" @click="removeParams(scope.row.attr_id)">删除</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>
            </el-tabs>
        </el-card>

<!--        添加参数对话框-->
        <el-dialog
            :title="'添加'+titleText"
            :visible.sync="addDialogVisible"
            width="50%"
            @close="addDialogClosed">
            <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item :label="titleText" prop="attr_name">
                    <el-input v-model="addForm.attr_name"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="addDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="addParams">确 定</el-button>
            </span>
        </el-dialog>

<!--        修改对话框-->
        <el-dialog
            :title="'修改'+titleText"
            :visible.sync="editDialogVisible"
            width="50%"
            @close="editDialogClosed">
            <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item :label="titleText" prop="attr_name">
                    <el-input v-model="editForm.attr_name"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="editParams">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

<script>
import goodsType from "@/views/goodsManagement/goodsType";

export default {
    name: "Params",
    data(){
        return{
            //商品分类列表
            paramsList:[],
        //    级联选择框的配置对象
            goodsParams:{
                value:'cat_id',
                label:'cat_name',
                children:'children'
            },

        // 级联选择框双向绑定到的数组
            selecteParamsKeys:[],
        //    activeName被激活的页签的名称
            activeName:'many',
        //    动态参数数据
            manyTableData:[],
            //    静态属性的数据
            onlyTableData:[],
            addDialogVisible: false,
        //动态参数的数据对象
            addForm: {},
        //    动态参数的验证规则
            addFormRules:{
                attr_name:[
                    { required: true, message: '请输入参数名称', trigger: 'blur' }
                ]
            },


        //    修改按钮
            editDialogVisible: false,
        //    修改的对象
            editForm:{},
        //    修改的表单验证规则对象
            editFormRules:{
                attr_name:[
                    { required: true, message: '请输入参数名称', trigger: 'blur' }
                ]
            },

        //    展开列的操作
        //控制文本框与按钮的切换
            inputVisible:false ,
        //    文本框中输入的内容
            inputValue:''
        }
    },
    created() {
        this.getGoodsParamsList()
    },
    methods:{
        //    获取所有商品分类数据列表
        async getGoodsParamsList(){
            const {data:res} = await this.$http.get('categories')
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            this.paramsList = res.data
        },
    //    级联选择框选中项变化会触发这个函数
         handleChange(){
           this.getParamsDataList()
        },
        //tab页签点击事件的处理函数
        handleTabClick(){
            this.getParamsDataList()
            console.log(this.activeName)
        },
    //    获取参数的列表数据
        async getParamsDataList(){
            //只允许选中第三级分类
            if(this.selecteParamsKeys.length !== 3 ){
                //选中的不是三级分类
                this.selecteParamsKeys = []
                this.manyTableData = []
                this.onlyTableData = []
                return
            }
            //证明是选中的是三级分类
            // console.log(this.selecteParamsKeys)
            //    根据所选分类的Id 和桑倩所处的面板,获取对应的参数
            const {data:res} = await this.$http.get(`categories/${this.goodsId}/attributes`,{params:{sel:this.activeName}})
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            // console.log(res.data)
            //attr_vals将这个字符串分割为数组形式
            res.data.forEach(item =>{
                item.attr_vals = item.attr_vals ?item.attr_vals.split(' '):[]
            //    控制文本框显示与隐藏
                item.inputVisible =false
            //    文本框中输入的值
                item.inputValue = ''

            })
            console.log(res.data)
            if(this.activeName === 'many'){
            //    获取到的是动态参数的数据
                this.manyTableData = res.data
            }
        //    获取到静态属性
            this.onlyTableData = res.data
        },
        //监听对话框的关闭事件
        addDialogClosed(){
            this.$refs.addFormRef.resetFields()
        },
        // 对话框确定按钮添加动态参宿和静态属性处理事件
        addParams(){
        //    表单预验证
            this.$refs.addFormRef.validate( async valid => {
                if(!valid){
                    return
                }

            //    发起请求
                const {data:res} = await this.$http.post(`categories/${this.goodsId}/attributes`,{attr_name: this.addForm.attr_name,attr_sel:this.activeName})
                if(res.meta.status !== 201){
                    return this.$message.error(res.meta.msg)
                }
                this.$message.success(res.meta.msg)
                this.addDialogVisible = false
                await this.getParamsDataList()

            })
        },

        //修改按钮点击显示对话框事件
        async showEditDialog(id){
            const {data:res} = await this.$http.get(`categories/${this.goodsId}/attributes/${id}`,{params:{attr_sel:this.activeName}})
            if(res.meta.status !== 200){
                return   this.$message.error(res.meta.msg)
            }
            this.editForm = res.data
            this.editDialogVisible = true
        },
        //重置修改表单
        editDialogClosed(){
            this.$refs.editFormRef.resetFields()
        },
    //    点击修改对话框确定按钮的事件
        editParams(){
            //表单预验证
            this.$refs.editFormRef.validate( async  valid=>{
                if(!valid){
                    return
                }
            //    发起请求
                const {data:res} = await this.$http.put(`categories/${this.goodsId}/attributes/${this.editForm.attr_id}`,{attr_name: this.editForm.attr_name,attr_sel:this.activeName})
                if(res.meta.status !== 200){
                    return this.$message.error(res.meta.msg)
                }
                this.$message.success(res.meta.msg)
                await this.getParamsDataList()
                this.editDialogVisible = false
            })

        },

        //删除操作
        async removeParams(attr_id){
            // 警告提示
            const confirmResult = await this.$confirm('此操作将永久删除该类型, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(err => err)

            //    如果用户确认删除,则返回值为字符串confirm
            //    如果用户点击取消,则返回值为cancel
            //     console.log(confirmResult)
            if(confirmResult !== 'confirm'){
                return this.$message.info('取消删除')
            }

            const {data:res} = await  this.$http.delete(`categories/${this.goodsId}/attributes/${attr_id}`)
            if(res.meta.status !== 200){
                return  this.$message.error(res.meta.msg)
            }

            await this.getParamsDataList()
            this.$message.success(res.meta.msg)

        },

        //    展开列的操作
        //文本框失去焦点,或者按回车键的事件
        handleInputConfirm(row){
            // console.log('ok')
            //文本框失去焦点后,应该显示为按钮状态
            if(row.inputValue.trim().length === 0){
                row.inputValue = ''
                row.inputVisible = false
                return
            }

        //    如过没有return,则证明输入的内容,需要被后续处理
            row.attr_vals.push(row.inputValue.trim())
            row.inputValue = ''
            row.inputVisible = false

        //    需要发起请求保存这次操作
            this.saveAttrVals(row)

        },
        //将attr_vals的操作,保存到数据库中,发起请求
        async saveAttrVals(row){
            const {data:res} = await this.$http.put(`categories/${this.goodsId}/attributes/${row.attr_id}`,{
                attr_name:row.attr_name,
                attr_sel:row.attr_sel,
                attr_vals:row.attr_vals.join(' ')
            })
            if(res.meta.status !== 200){
                return  this.$message.error(res.meta.msg)
            }
            this.$message.success(res.meta.msg)
        },
        //点击按钮,显示按钮
        showInput(row){
            row.inputVisible =true
        //    让文本框自动获取焦点
        //    $nextTick方法的作用,就是当页面上元素被重新渲染之后,才会指定回调函数中的代码
            this.$nextTick(_ => {
                this.$refs.saveTagInput.$refs.input.focus();
            })
        },
        //删除对应的参数可选项
        handleChanged(index,row){
            row.attr_vals.splice(index,1)
            //进行删除操作后,把这次操作保存到数据库
            this.saveAttrVals(row)
        }
    },

//    计算属性
    computed : {
        //    如果按钮需要被禁用,则返回true,否则返回false
        isBtnDisabled() {
            if (this.selecteParamsKeys.length !== 3) {
                return true
            }
            return false
        },

        //当前选中的三级分类的Id
        //分类Id
        goodsId() {
            if (this.selecteParamsKeys.length === 3) {
                return this.selecteParamsKeys[this.selecteParamsKeys.length - 1]
            }
            return null
        },

        //    动态添加对话框名称
        titleText() {
            if (this.activeName === 'many') {
                return '动态参数'
            }
            return '静态属性'
        },
    }
}
</script>

<style scoped>
.el-alert{
    line-height: 100%;
}
.goods_opt{
    margin: 15px 0;

}
.goods_col{
    display: flex;
    justify-content: left;
}
.el-tabs{
    line-height: 100%;
}
.btns{
    display: flex;
    justify-content: left;
}
.el-tag{
    margin: 15px;
}
.input-new-tag{
    width: 150px;
}
</style>

商品分类

<template>
    <div>
        <!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>商品管理</el-breadcrumb-item>
            <el-breadcrumb-item>商品分类</el-breadcrumb-item>
        </el-breadcrumb>

<!--        卡片视图区-->
        <el-card>
<!--            添加商品按钮-->
            <el-row>
                <el-col>
                    <el-button type="success" @click="showGoodsDialogVisible">添加分类</el-button>
                </el-col>
            </el-row>

<!--            带有树形网格的VUE表格区-->
            <tree-table :data="goodsList"
                        :columns="columns"
                        :selection-type="false"
                        :expand-type="false"
                        border
                        stripe
                        show-index
                        index-text="#"
                        :show-row-hover="false">
                <template slot="isOk" slot-scope="scope">
                    <i class="el-icon-success" v-if="scope.row.cat_deleted === false"
                        style="color: lightgreen"></i>
                    <i class="el-icon-error" v-else style="color: red"></i>
                </template>

                <template slot="sort" slot-scope="scope">
                    <el-tag v-if="scope.row.cat_level === 0">标签一</el-tag>
                    <el-tag v-else-if="scope.row.cat_level === 1" type="success">标签一</el-tag>
                    <el-tag v-else  type="danger">标签一</el-tag>
                </template>

                <template slot="operation" slot-scope="scope">
                    <el-button size="min" type="success" icon="el-icon-edit" @click="showEditGoods(scope.row.cat_id)">编辑</el-button>
                    <el-button size="min" type="danger" icon="el-icon-error" @click="removeGoods(scope.row.cat_id)">删除</el-button>
                </template>
            </tree-table>
<!--            分页区-->
            <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current-page="querInfo.pagenum"
                :page-sizes="[1,2,3,4,5,10]"
                :page-size="querInfo.pagesize"
                layout="total, sizes, prev, pager, next, jumper"
                :total="total">
            </el-pagination>
        </el-card>

<!--        添加分类对话框-->
        <el-dialog
            title="添加分类"
            :visible.sync="addGoodsDialogVisible"
            width="50%"
            @close="addDialogClose">
            <el-form :model="goodsFrom" :rules="goodsFromRules" ref="goodsFromRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="分类名称" prop="cat_name">
                    <el-input v-model="goodsFrom.cat_name"></el-input>
                </el-form-item>
                <el-form-item label="父级分类" >
<!--                    options指定数据源-->
<!--                    props用来指定配置对象的-->
                    <el-cascader
                        :options="parenGoodsList"
                        expand-trigger="hover"
                        :props="caseGoodsProps"
                        v-model="selectKeys"
                        @change="parentGoodsChange"
                        clearable
                        change-on-select>

                    </el-cascader>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="addGoodsDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="addGoods">确 定</el-button>
            </span>
        </el-dialog>


<!--        编辑对话框-->
        <el-dialog
            title="编辑"
            :visible.sync="editDialogVisible"
            width="50%"
            @close="editDialogClose">
            <el-form :model="editFrom" :rules="editFromRules"
                     ref="editFromRef" label-width="100px"
                     class="demo-ruleForm">
                <el-form-item label="分类名称" prop="cat_name">
                    <el-input v-model="editFrom.cat_name"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="editGoods">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

<script>
export default {
    name: "goodsType",
    data(){
        return{
            //查询条件
            querInfo:{
                type : 3,
                pagenum:1,
                pagesize:5
            },
            // 商品分类数据列表goodsList
            goodsList:[],
        //    总数据条数
            total: 0,

        //为tree-table指定列名的定义
            columns:[
                //第一列
                {
                    label:'分类名称',
                    prop:'cat_name'
                },
            //    第二列 是否有效
                {
                    label: '是否有效',
                //    表示:当前列定义为模板列,也就是要是用到定义域插槽
                    type:'template',
                    template: 'isOk'
                },

            //    第三列  排序 用到定义域插槽
                {
                    label: '排序',
                    type:'template',
                    template: 'sort'
                },
            //    第四列 操作  作用域插槽
                {
                    label: '操作',
                    type:'template',
                    template: 'operation'
                }
            ],

        //    添加分类
            addGoodsDialogVisible: false,
            //添加分类的数据对象
            goodsFrom:{
                cat_name:'',
            //    父级分类Id
                cat_pid:0,
            //    当前分类等级
                cat_level: 0
            },
            goodsFromRules:{
                cat_name:[
                    { required: true, message: '请输入分类名称', trigger: 'blur' }
                ]
            },
            //父级分类的列表
            parenGoodsList:[],
        //    指定级联选择器的配置对象
            caseGoodsProps:{
                // :props="caseGoodsProps"   props 的参数
                // value指定选项的值为选项对象的某个属性值
                // label指定选项标签为选项对象的某个属性值
                // children指定选项的子选项为选项对象的某个属性值
              value:'cat_id',
              label:'cat_name',
              children : 'children'
            },
        //    选中的父级分类Id数组
            selectKeys:[],


        //    编辑操作
            editDialogVisible: false,
            // 编辑数据列表
            editFrom: {},
            editFromRules:{
                cat_name:[
                    { required: true, message: '请输入分类名称', trigger: 'blur' }
                ]
            }
        }
    },
    created() {
        this.getGoodsList()
    },

    methods:{
        //请求获取商品分类数据列表
        async getGoodsList(){
           const {data:res} = await this.$http.get('categories',{params:this.querInfo})
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }

        //    获取商品分类数据列表成功
            this.goodsList = res.data.result
            this.total = res.data.total

        },


    //    分页
        handleSizeChange(newSize){
            this.querInfo.pagesize = newSize
            this.getGoodsList()
        },
        handleCurrentChange(newNum){
            this.querInfo.pagenum = newNum
            this.getGoodsList()
        },


        //添加分类操作
        showGoodsDialogVisible(){
           // 先获取父级分类的数据列表,
            this.getParentGoodsList()
           this.addGoodsDialogVisible = true
        },
        //获取父级分类的数据列表
        async getParentGoodsList(){
          const {data:res} =await  this.$http.get('categories',{params:{type:2}})
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            // console.log(res.data)data
            this.parenGoodsList = res.data
        },
        //选择项发生变化触发这个函数
        parentGoodsChange(){
            // console.log(this.selectKeys)
        //    如果selectKeys数组中的length大于0  证明选中了父级分类
        //    如果selectKeys数组中的length等于0  证明没有选中父级分类
            if(this.selectKeys.length > 0){
                this.goodsFrom.cat_pid = this.selectKeys[this.selectKeys.length-1]
                //位当前分类的等级赋值
                this.goodsFrom.cat_level = this.selectKeys.length
                return
            }
            this.goodsFrom.cat_pid = 0
            this.goodsFrom.cat_level = 0

        },
        //添加分类点击按钮
        addGoods(){
           // 表单预验证
           this.$refs.goodsFromRef.validate( async valid =>{
               if(!valid){
                   return
               }

               //    请求数据
               const {data :res} = await this.$http.post('categories',{cat_pid:this.goodsFrom.cat_pid,cat_name:this.goodsFrom.cat_name,cat_level:this.goodsFrom.cat_level})
               if(res.meta.status !== 201){
                   return   this.$message.error(res.meta.msg)
               }

               await this.getGoodsList()
               this.addGoodsDialogVisible = false
           })
        },
        //关闭对话框后,重置清空对话框
        addDialogClose(){
            this.$refs.goodsFromRef.resetFields()
            this.selectKeys = []
            this.goodsFrom.cat_pid = 0
            this.goodsFrom.cat_level = 0
        },


    //    编辑
        async showEditGoods(id){
            const {data:res} = await this.$http.get('categories/'+id)
            if(res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            // console.log( res.data)
            this.editFrom = res.data
            this.editDialogVisible = true
        },
        editDialogClose(){
            this.$refs.editFromRef.resetFields()
        },
    //    确定按钮
        editGoods(){
            // 表单预验证
            this.$refs.editFromRef.validate( async valid =>{
                if(!valid){
                    return
                }

               // 发起请求
                const  {data:res} = await this.$http.put('categories/'+this.editFrom.cat_id,{cat_name:this.editFrom.cat_name})
                if(res.meta.status !== 200){
                    return this.$message.error(res.meta.msg)
                }
                await this.getGoodsList()
                this.editDialogVisible = false
                this.$message.success(res.meta.msg)
            })
        },


    //    删除操作
        async removeGoods(id){
            //提示弹框
            const confirmResult = await this.$confirm('此操作将永久删除该类型, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(err => err)

            //    如果用户确认删除,则返回值为字符串confirm
            //    如果用户点击取消,则返回值为cancel
            //     console.log(confirmResult)
            if(confirmResult !== 'confirm'){
                return this.$message.info('取消删除')
            }

            const {data:res} = await this.$http.delete('categories/'+id)
            if(res.meta.status !== 200){
                return  this.$message.error(res.meta.msg)
            }

            this.$message.success(res.meta.msg)
            await this.getGoodsList()

        }
    }
}
</script>

<style scoped>
.el-cascader{
    width: 100%;
}
</style>

订单列表

<template>
    <div>
        <!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>订单管理</el-breadcrumb-item>
            <el-breadcrumb-item>订单列表</el-breadcrumb-item>
        </el-breadcrumb>

<!--        卡片区-->
        <el-card>
<!--            搜索框-->
            <el-row>
                <el-col :span="8">
                    <el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="searchOrders">
                        <el-button slot="append" icon="el-icon-search" @click="searchOrders"></el-button>
<!--                        点击后触发clear清空事件-->
                    </el-input>
                </el-col>
            </el-row>

<!--            表格区-->
            <el-table
                :data="orderList"
                stripe
                border
                style="width: 100%">
                <el-table-column
                    type="index"
                    label="#">
                </el-table-column>
                <el-table-column
                    prop="order_number"
                    label="订单编号">
                </el-table-column>
                <el-table-column
                    prop="order_price"
                    label="订单价格"
                    width="180">
                </el-table-column>
                <el-table-column
                    prop="pay_status"
                    label="是否付款"
                    width="180">
                    <template slot-scope="scope">
                        <el-tag type="danger" v-if="scope.row.pay_status==='0'">未付款</el-tag>
                        <el-tag type="success" v-else>已付款</el-tag>
                    </template>
                </el-table-column>
                <el-table-column
                    prop="is_send"
                    label="是否发货"
                    width="180">
                </el-table-column>
                <el-table-column
                    prop="create_time"
                    label="下单时间"
                    width="200">
                    <template slot-scope="scope">
                        <!--                        时间格式 通过时间过滤器来装换-->
                        {{scope.row.update_time | dataFormat}}
                    </template>
                </el-table-column>
                <el-table-column
                    label="操作"
                    width="180">
                    <template slot-scope="scope">
                        <el-button type="primary" icon="el-icon-edit" circle @click="showAddress(scope.row.order_id)"></el-button>
                        <el-button type="success" icon="el-icon-location" circle @click="showDetailsBox"></el-button>
                    </template>
                </el-table-column>
            </el-table>

<!--            分页区-->
            <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current-page="queryInfo.pagenum"
                :page-sizes="[5, 10, 15, 20]"
                :page-size="queryInfo.pagesize"
                layout="total, sizes, prev, pager, next, jumper"
                :total="total">
            </el-pagination>
        </el-card>

<!--        编辑对话框-->
        <el-dialog
            title="修改地址"
            :visible.sync="editDialogVisible"
            width="30%"
            @close="addressHandleClosed">
            <el-form :model="addressForm" :rules="addressFormRules" ref="addressFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="省市区/县" prop="address1">
                    <el-cascader
                        v-model="addressForm.address"
                        :options="cityData"
                        :props="{ expandTrigger: 'hover' } ">
                    </el-cascader>
                </el-form-item>
                <el-form-item label="详细地址" prop="consignee_addr">
                    <el-input v-model="addressForm.address2"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="editAddress">确 定</el-button>
            </span>
        </el-dialog>

<!--     查看订单详情   -->
        <el-dialog
            title="查看订单详情"
            :visible.sync="orderDetailsDialogVisible"
            width="50%">
            <el-timeline>
                <el-timeline-item
                    v-for="(activity, index) in getOrderDetails"
                    :key="index"
                    :timestamp="activity.time">
                    {{activity.context}}
                </el-timeline-item>
            </el-timeline>
        </el-dialog>
    </div>
</template>

<script>
// <!--导入城市信息-->
import cityData from './citydata'
export default {
    name: "orderList",
    data(){
        return{
            // 获取到的订单列表的数据
            orderList:[],
            // 获取订单列表的参数
            queryInfo:{
                query:'',
                pagenum:1,
                pagesize:10
            },
            total:0,

        //    修改地址
            editDialogVisible:false,
            addressForm:{
                address1:[],
                address2:'',
                is_send:'',
                order_pay:'',
                order_price:0,
                order_number:'',
                pay_status:''
            },
            //城市信息
            cityData,
            addressFormRules:{
                address1:[
                    { required: true, message: '请选择省市区县', trigger: 'blur' }
                ],
                address2: [
                    { required: true, message: '请输入详细地址', trigger: 'blur' }
                ]
            },

        //    订单的id
            getOrderId:0,

            orderDetailsDialogVisible:false,
            //物流信息
            getOrderDetails:[]


        }
    },
    created() {
        this.getOrderList()
    },
    methods:{
        //获取订单表单数据
        async getOrderList(){
            const {data:res} = await this.$http.get('orders', {params:this.queryInfo})
            if (res.meta.status !== 200){
                return this.$message.error(res.meta.msg)
            }
            // console.log(res)
            this.orderList = res.data.goods
            // console.log(this.orderList)
            this.total = res.data.total
        },
        // 分页
        //当页面的条数改变时会触发这个事件
        handleSizeChange(newSize){
            this.queryInfo.pagesize = newSize
            this.getOrderList()
        },
        //当页面的当前页面发生改变时,会触发这个事件,即选择下一页
        handleCurrentChange(newNum){
            this.queryInfo.pagenum = newNum
            this.getOrderList()
        },

        //查询输入框
        searchOrders(){
            this.getOrderList()
        },

        //编辑对话框
        addressHandleClosed(){
            //关闭编辑对话框后,清空对话框内容
            this.$refs.addressFormRef.resetFields()
        },
        showAddress(id){
            this.getOrderId = id
            this.editDialogVisible=true
        },
        editAddress(){
            //表单预验证
            this.$refs.addressFormRef.validate(async valid=>{
                if(!valid){
                    return
                }

                //发起请求
                const {data:res} = await this.$http.put('orders/'+this.getOrderId,{params:this.addressForm})
                if (res.meta.status !== 200){
                    return this.$message.error(res.meta.msg)
                }
                this.$message.success(res.meta.msg)
                await this.getOrderList()
                this.editDialogVisible = false
            })

        },
        async showDetailsBox(){
            // const {data:res} = await this.$http.get('/kuaidi/1106975712662')
            // if (res.meta.status !== 200){
            //     return this.$message.error('获取物流信息失败!')
            // }
            // this.getOrderDetails = res.data
            this.orderDetailsDialogVisible =true
        },

    }
}
</script>

<style scoped>
.el-cascader{
    width: 100%;
}
</style>

数据报表

需要用到echarts

<!--        卡片区-->
        <el-card>
<!--            报表视图-->
            <!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
            <div id="main" style="width: 100%;height:400px;"></div>
        </el-card>
  • 从后台获取数据
  • mounted钩子函数,表示当页面上的数据全部被渲染完毕之后会被调用
//mounted钩子函数,表示当页面上的数据全部被渲染完毕之后会被调用
    async mounted() {
        // 3. 基于准备好的dom,初始化echarts实例
        var myChart = echarts.init(document.getElementById('main'));
        //发起get请求,获取数据
        const {data:res} = await this.$http.get('reports/type/1')
        if(res.meta.status !== 200){
            return this.$message.error(res.meta.msg)
        }

        //4. 指定图表的配置项和数据
        const result = _.merge(res.data,this.options)
        
            //5. 使用刚指定的配置项和数据显示图表。
            myChart.setOption(result);
    },
data(){
        return{
            // 需要合并的选项
            options: {
                title: {
                    text: '用户来源'
                },
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                        type: 'cross',
                        label: {
                            backgroundColor: '#E9EEF3'
                        }
                    }
                },
                grid: {
                    left: '3%',
                    right: '4%',
                    bottom: '3%',
                    containLabel: true
                },
                xAxis: [
                    {
                        boundaryGap: false
                    }
                ],
                yAxis: [
                    {
                        type: 'value'
                    }
                ]
            }
        }
       }
  • 数据报表源码
<template>
    <div>
        <!--        面包屑-->
        <el-breadcrumb separator-class="el-icon-arrow-right">
            <el-breadcrumb-item :to="{path:'/home'}">首页</el-breadcrumb-item>
            <el-breadcrumb-item>数据管理</el-breadcrumb-item>
            <el-breadcrumb-item>数据报表</el-breadcrumb-item>
        </el-breadcrumb>

<!--        卡片区-->
        <el-card>
<!--            报表视图-->
            <!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
            <div id="main" style="width: 100%;height:400px;"></div>
        </el-card>
    </div>
</template>

<script>
// 1.导入echarts
import  * as echarts from 'echarts';
//导入这个组件用来合并,options和res.data,来确保图表中存在鼠标跟随等事件的发生
import _ from 'lodash'
export default {
    name: "dataReport",
    data(){
        return{
            // 需要合并的选项
            options: {
                title: {
                    text: '用户来源'
                },
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                        type: 'cross',
                        label: {
                            backgroundColor: '#E9EEF3'
                        }
                    }
                },
                grid: {
                    left: '3%',
                    right: '4%',
                    bottom: '3%',
                    containLabel: true
                },
                xAxis: [
                    {
                        boundaryGap: false
                    }
                ],
                yAxis: [
                    {
                        type: 'value'
                    }
                ]
            }
        }
    },
    created() {
    },
    //mounted钩子函数,表示当页面上的数据全部被渲染完毕之后会被调用
    async mounted() {
        // 3. 基于准备好的dom,初始化echarts实例
        var myChart = echarts.init(document.getElementById('main'));
        //发起get请求,获取数据
        const {data:res} = await this.$http.get('reports/type/1')
        if(res.meta.status !== 200){
            return this.$message.error(res.meta.msg)
        }

        //4. 指定图表的配置项和数据
        const result = _.merge(res.data,this.options)
        // var option = {
        //     title: {
        //         text: 'ECharts 入门示例'
        //     },
        //     tooltip: {},
        //     legend: {
        //         data:['销量']
        //     },
        //     xAxis: {
        //         data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
        //     },
        //     yAxis: {},
        //     series: [{
        //         name: '销量',
        //         type: 'bar',
        //         data: [5, 20, 36, 10, 10, 20]
        //     }]
        // };
            //5. 使用刚指定的配置项和数据显示图表。
            myChart.setOption(result);
    },
    methods:{

    }
}
</script>

<style scoped>

</style>

在这里插入图片描述

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './assets/CSS/global.css'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css';
import axios from "axios"
import 'default-passive-events'
//为项目添加进度条
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
//警告提示框
import { MessageBox } from 'element-ui'
import TreeTable from 'vue-table-with-tree-grid'
// 带有树形网格的VUE表

//导入富文本编辑器
import VueQuillEditor from 'vue-quill-editor'
//导入富文本编辑器的样式
// require styles
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'



//配置请求的根路径,也就是API接口文档中的接口基准地址
axios.defaults.baseURL = "http://127.0.0.1:8888/api/private/v1/"
//请求拦截器
//通过axios请求拦截器添加token,保证拥有获取数据的权限 显示进度条NProgress.start()
axios.interceptors.request.use(config=>{
    NProgress.start()
    // console.log(config)
    config.headers.Authorization = window.sessionStorage.getItem('token')
    return config
})
// response 拦截器中(这个拦截器表示获取数据成功),  隐藏进度条NProgress.done()
axios.interceptors.response.use(config => {
    NProgress.done()
    return config
})
Vue.config.productionTip = false
Vue.use(Element);
Vue.prototype.$http=axios;
//MessageBox.confirm弹框的引用
Vue.prototype.$confirm = MessageBox.confirm
// 带有树形网格的VUE表
Vue.component('tree-table', TreeTable)
//格式化时间,时间过滤器
Vue.filter('dataFormat', function (originVal) {
    const dt = new Date(originVal)

    const y = dt.getFullYear()
    const m = (dt.getMonth() + 1 + '').padStart(2, '0')
    const d = (dt.getDate() + '').padStart(2, '0')

    const hh = (dt.getHours() + '').padStart(2, '0')
    const mm = (dt.getMinutes() + '').padStart(2, '0')
    const ss = (dt.getSeconds() + '').padStart(2, '0')
    // yyyy-mm-dd hh:mm:ss
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})

//添加富文本编辑器为全局变量。
Vue.use(VueQuillEditor)
new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')

路由index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from "../views/Login";
import Home from "@/views/Home";
import User from "../views/user/User";
import Welcome from "../views/Welcome/Welcome";
import Rights from  "../views/power/Rights"
import Roles from "../views/power/Roles"
import goodsType from "@/views/goodsManagement/goodsType";
import Params from "@/views/goodsManagement/Params";
import List from "@/views/goodsManagement/List";
import Add from "@/views/goodsManagement/Add";
import orderList from "@/views/order/orderList";
import dataReport from "@/views/dataReport/dataReport";
Vue.use(VueRouter)

const routes = [
  {  //路由重定向
    path: '/',
    redirect: '/login'
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  },
  {
    path:'/home',
    props:true,  //获取name
    name:'Home',
    //路由重定向
    redirect:'/welcome',
    component: Home,
    children:[
      {
        path:'/welcome',
        name:'Welcome',
        component:Welcome
      },
      {
        path:'/users',
        name:'User',
        component: User

      },
      {
        path: '/rights',
        name: 'Rights',
        component: Rights
      },
      {
        path: '/roles',
        name: 'Roles',
        component: Roles
      },
      {
        path: '/categories',
        name: 'goodsType',
        component: goodsType
      },
      {
        path: '/params',
        name:'Params',
        component: Params
      },
      {
        path: '/goods',
        name:'List',
        component: List
      },
      {
        path: '/goods/add',
        name:'Add',
        component: Add
      },
      {
        path: '/orders',
        name:'orders',
        component: orderList
      },
      {
        path:'/reports',
        name:'dataReport',
        component: dataReport
      }
    ]

  }

]
const router = new VueRouter({
  routes
})

//路由导航守卫控制访问权限
//如果用户没有登录,但是直接通过URL访问特定页面,需要重新导航到登录页面。
//为路由对象,添加beforEach 导航守卫
// router.beforeEach((to,from,next)=>{});

//开始路由守卫挂载
router.beforeEach((to,from,next)=>{
 // to表示将要访问的路径
//  from表示原路径
//  next表示是一个函数,表示放行
//  next()放行,,next(‘/login')强制跳转
  if(to.path ==='/login')
    return next();
//   获取token
  const tokenStr= window.sessionStorage.getItem('token')
//  没有token,强制跳转到登录页面
  if(!tokenStr)
    return next('/login');
  //有token直接放行
  next();
})
export default router

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐