项目初始化

element的步骤条修改样式_javascript


1 安装Vue 脚手架

npm install -g @vue/cli

2 通过Vue脚手架创建项目

输入vue ui 进入vue管理界面

element的步骤条修改样式_vue.js_02


选择创建 在指定的目录下创建项目

element的步骤条修改样式_javascript_03


element的步骤条修改样式_ico_04


选择手动预设

element的步骤条修改样式_vue.js_05


勾选以下这些功能

element的步骤条修改样式_vue.js_06


element的步骤条修改样式_vue.js_07


element的步骤条修改样式_javascript_08


element的步骤条修改样式_element的步骤条修改样式_09


点击下一步后

element的步骤条修改样式_vue.js_10


点击创建项目

element的步骤条修改样式_javascript_11


3 配置element-ui组件库

element的步骤条修改样式_vue.js_12


配置插件 将其改为按需导入

element的步骤条修改样式_javascript_13


5 配置axios库

安装axios运行依赖

element的步骤条修改样式_html5_14


将代码托管至远程github仓储

后台项目环境配置

element的步骤条修改样式_javascript_15


安装phpstudy

element的步骤条修改样式_javascript_16


配置安装数据库

解压vue_api_server文件 用phpstudy执行db文件夹下的sql脚本去得到一个数据库

element的步骤条修改样式_javascript_17


element的步骤条修改样式_ico_18


默认密码为root,要还原的文件为mydb.sql,还原到的数据库名要和文件名一致 点击导入

element的步骤条修改样式_ico_19


验证导入是否成功

element的步骤条修改样式_javascript_20


该目录下有mydb文件夹 并且文件夹中有相关文件说明导入成功在api_vue_server目录下安装所有的依赖包

element的步骤条修改样式_javascript_21


启动后台项目

element的步骤条修改样式_ico_22


使用postman测试接口是否正常安装postman

根据api文档验证登录接口

在url处输入本机请求基准地址(http://127.0.0.1:8888/api/private/v1/)作为根路径 在根路径后加上登录的地址(login) 选择post方式提交数据

element的步骤条修改样式_javascript_23


选择body 以及数据提交的格式 输入要提交的数据 后点击send发送

element的步骤条修改样式_javascript_24


没有改用户 输入一个正确的用户名和密码

element的步骤条修改样式_javascript_25


token为客户端和服务器的状态保持机制 可以根据token来判断是否登录

登录功能

element的步骤条修改样式_html5_26


element的步骤条修改样式_javascript_27


何时使用token的方式维持状态:

如果前端和后端的接口存在跨域问题 那么就要使用token来维持登录状态token原理分析

element的步骤条修改样式_vue.js_28


客户端和服务器都是通过token值来进行校验的绘制登录界面

element的步骤条修改样式_html5_29


打开项目后 用git status判断工作区是否干净 如果是干净的就可以进行登录模块的项目开发

注意在开发一个功能模块时 都需要为其创建一个分支 开发完毕后在将其合并到主分支

为登录模块创建一个分支

element的步骤条修改样式_html5_30


查看当前分支 星号表示当前正处于的分支

element的步骤条修改样式_ico_31


查看项目运行效果打开任务运行serve命令 编译成功后点击启动app

element的步骤条修改样式_vue.js_32


会得到一个默认的项目yemia

element的步骤条修改样式_vue.js_33


梳理项目结构删除app,vue里的默认代码 清空页面内容

element的步骤条修改样式_ico_34


清空路由文件

index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

export default new VueRouter({
  routes: []
})

删除components的helloworld组件

关闭eslint rules的代码校验功能
在.eslintrc中删除vue/standard

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    // '@vue/standard'
  ],
  parserOptions: {
    parser: 'babel-eslint'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  }
}

在components下创建Login.vue子组件

<template>
    <div>
        登录组件
    </div>
</template>
<script>
export default {}
</script>
<style lang="less" scoped>

</style>

配置路由关系

import Vue from 'vue'
import VueRouter from 'vue-router'

import login from '../components/Login.vue'

Vue.use(VueRouter)

export default new VueRouter({
  routes: [
  	 {path:'/',redirect:"/login"},
    {path:"/login",component:login}
  ]
})

app.vue

<template>
  <div id="app">
     <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app'
}
</script>

<style>

</style>

运行

element的步骤条修改样式_javascript_35


设置样式

login.vue

<style lang="less" scoped>
.logon_container{
    background-color: #2b4b6b;
}
</style>

此时编译会报错 没有配置less-loader

在vue配置面板 安装less-loader为运行依赖

element的步骤条修改样式_vue.js_36


安装less开发依赖

element的步骤条修改样式_javascript_37


在vue ui里安装less和less-loader 运行后报错

只能在命令行用使用yarn add less-loader -D 和yarn add less -D

设置背景颜色并绘制登录盒子
在assets下新建一个全局样式文件global.css

/* 全局样式表 */
html,body,#app{
    height: 100%;
    margin: 0;
    padding: 0;
}

在main.js导入改样式表

//导入全局样式表
import './assets/css/global.css'

Login.vue

<template>
    <div class="logon_container">
        <div class="login_box">

        </div>
    </div>
</template>
<style lang="less" scoped>
.logon_container{
    height: 100%;
    background-color: #2b4b6b;
}
.login_box{
    width: 450px;
    height: 300px;
    background-color: #fff;
    border-radius: 3px;
    position: absolute;
    left: 50%;
    top: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 #ddd;
        position: absolute;
        left: 50%;
        background-color: #fff;
        transform: translate(-50%,-50%);
        img{
            width: 100%;
            height: 100%;
            // 给图片也加圆角
            border-radius:50%;
            background-color: #eee;
        }
    }
}

element的步骤条修改样式_html5_38

  • 绘制表单元素调整布局
    使用element-ui的form表单的代码 并修改
<!-- 登录表单区域 -->
            <!-- 使用element-ui实现登录表单  -->
            <el-form  label-width="0px" class="login_form">
                <!-- 用户名 -->
                <!-- el-form-item表示表单项 -->
                <el-form-item>
                    <el-input></el-input>
                </el-form-item>
                <!-- 密码 -->
                <el-form-item>
                    <el-input></el-input>
                </el-form-item>
                <!-- 按钮 -->
                <el-form-item class="btns">
                    <!-- 使用element-ui的按钮 -->
                    <el-button type="primary">登录</el-button>
                    <el-button type="info">重置</el-button>
                </el-form-item>
            </el-form>

在element.js按需导入组件

import Vue from 'vue'
import { Button } from 'element-ui'

import {Form,FormItem} from 'element-ui'
import {Input} from 'element-ui'

Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)

设置样式

.login_form{
    position: absolute;
    bottom:0;
    width: 100%;
    padding: 0 20px;
    // 这里默认的form元素是box-sizing:content
    box-sizing: border-box;

}
.btns{
    display: flex;
    // 主轴上对其方式居右对其
    justify-content: flex-end;
}

element的步骤条修改样式_html5_39


给输入框内添加小图标 根据element-ui提供的使用图标方式 并使用阿里图标库

导入css图标文件

main.js

//导入字体图标样式
import './assets/fonts/iconfont.css'
<el-form-item>
                    <el-input prefix-icon="icon iconfont icon-user">       
                    </el-input>
                </el-form-item>
                <!-- 密码 -->
                <el-form-item>
                    <!--根据element-ui提供的使用图标方式 并使用阿里图标库 -->
                    <el-input  prefix-icon="icon iconfont icon-3702mima"></el-input>
                </el-form-item>

element的步骤条修改样式_javascript_40

  • 实现表单双向数据绑定
    根据element-ui表单数据绑定 首先给el-form添加属性绑定 :model=loginForm
<el-form :model="loginForm" label-width="0px" class="login_form">

在data中分别定义属性

data(){
        return{
            //登录表单的数据绑定对象
            loginForm:{
                username:'zs',
                password:'shu'
            }
        }
    }

在给每一个输入框通过v-model双向数据绑定loginForm中的某一个属性 其中给密码框指定type值将密码设为不可见

<el-form-item>
                    <el-input v-model="loginForm.username" prefix-icon="icon iconfont icon-user">       
                    </el-input>
                </el-form-item>
                <!-- 密码 -->
                <el-form-item>
                    <!--根据element-ui提供的使用图标方式 并使用阿里图标库 -->
                    <el-input type="password" v-model="loginForm.password" prefix-icon="icon iconfont icon-3702mima"></el-input>
                </el-form-item>

element的步骤条修改样式_html5_41

  • 表单输入内容校验

根据element-ui提供的文档 首先绑定rules属性

<el-form  :rules="loginFormRules" :model="loginForm" label-width="0px" class="login_form">

定义表单验证规则对象

//这是表单验证规则对象
            loginFormRules:{
                //验证用户名是否合法
                username:[
                        // required表示必填项 blur表示鼠标离开的时候校验
                      { required: true, message: '请输入登录名称', trigger: 'blur' },
                       { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
                ],
                password:[
                    
                         { required: true, message: '请输入密码', trigger: 'blur' },
                       { min: 6, max: 11, message: '长度在 6 到 11 个字符', trigger: 'blur' }
                    
                ],
                
            }

注意 表单验证对象里数组的名称必须要和v-model绑定的数据对象的名称一致 例如: 用户名输入框 的v-model绑定的值为loginForm.username 所以 loginFormRules里的名称也要为username
通过props来引用

<el-form-item prop="username">
                    <el-input v-model="loginForm.username" prefix-icon="icon iconfont icon-user">       
                    </el-input>
                </el-form-item>
                <!-- 密码 -->
                <el-form-item prop="password">
                    <!--根据element-ui提供的使用图标方式 并使用阿里图标库 -->
                    <el-input type="password" v-model="loginForm.password" prefix-icon="icon iconfont icon-3702mima"></el-input>
                </el-form-item>

element的步骤条修改样式_ico_42

  • 实现表单的重置功能

根据element api文档首先指定ref

<el-form ref="loginFormRef"  :rules="loginFormRules" :model="loginForm" label-width="0px" class="login_form">

给重置按钮绑定事件

<el-button type="info" @click="resetLoginForm">重置</el-button>
methods:{
                //点击重置按钮重置表单
                resetLoginForm(){
                    this.$refs.loginFormRef.resetFields()
                }
            }
  • 登录时进行表单登录预验证

给登录按钮绑定事件

<el-button type="primary" @click="login">登录</el-button>

根据api使用validat方法 参数为一个回调函数 验证成功valid值为true否则为false

login(){
                    this.$refs.loginFormRef.validate((valid)=>{
                        console.log(valid)
                    })
                }
  • 配置axios发起登录请求

全局配置axios

//全局配置axios
import axios from 'axios'
//设置请求根路径
axios.defaults.baseURL='http://127.0.0.1:8888/api/private/v1/'
Vue.prototype.$http=axios

首先判断valid值 如果为false 则直接return

this.$refs.loginFormRef.validate(valid=>{
         if(!valid) return;

获取返回的值

const result=this.$http.post("login",this.loginForm);

此时返回的值为Promise 如果某一个方法返回的是Promise可以用await async来简化这次操作 await只能用在被async修饰的方法中

this.$refs.loginFormRef.validate(async valid=>{
      if(!valid) return;

      const result=await this.$http.post("login",this.loginForm);
      console.log(result)

此时获取到到的result的值为Promise封装好的数据 只有里面的data才是服务器提供的真正数据

element的步骤条修改样式_element的步骤条修改样式_43


我们只需要data值 所以将返回的值解构赋值为一个data属性 将其重命名为res

const {data:res}=await this.$http.post("login",this.loginForm);
 console.log(res)

element的步骤条修改样式_vue.js_44


根据res的状态值来判断登录是否成功

login(){
                    this.$refs.loginFormRef.validate(async valid=>{
                       
                        if(!valid) return;

                      
                        const {data:res}=await this.$http.post("login",this.loginForm);

                        if(res.meta.status!==200) return console.log("登录失败")
                        console.log("登录成功")

                    })
                }
  • 弹框提示

导入弹框组件 并挂载到构造原型对象上 使之可以用this来调用方法

import {Message} from 'element-ui'
Vue.prototype.$message=Message
if(res.meta.status!==200) return this.$message.error("登录失败!")
     this.$message.success('登录成功!')

1 将登录成功之后的token 保存到客户的seesionStorage中(token为确定登录后服务器提供的令牌 之后访问都要携带token值)

//1 1 项目中除了登录之外的其他API接口 必须在登录之后才能访问

//1.2 token只应当在当前网站打开期间生效 所以将token保存在sesssionStorage中(localStorage是持久化的存储机制 sessionStorage是会话时的存储机制)

//token属性在res的data属性中

element的步骤条修改样式_element的步骤条修改样式_45

window.sessionStorage.setItem('token',res.data.token)

element的步骤条修改样式_vue.js_46

创建Home组件

<template>
    <div>
        home
    </div>
</template>
<script>
export default {
    
}
</script>
<style lang="less" scoped>

</style>

通过编程式导航跳转到后台主页 路由地址是/home
Login.vue

this.$router.push('/home')

创建对应路由

{path:"/home",component:home}
  • 路由导航首位控制访问权限
    如果用户没有登录 但是直接通过URL(例如直接访问登录后的:http://localhost:8080/#/home地址)访问特定页面 需要重新导航到登录页面

改造路由 并挂载路由导航守卫
index.js

const router= new VueRouter({
  routes: [
     {path:'/',redirect:"/login"},
    {path:"/login",component:login},
    {path:"/home",component:home}
  ]
})

//挂载路由导航守卫
router.beforeEach((to,from,next)=>{
  //to 将要访问的路径
  //from 代表从那个路径跳转而来
  //next 是一个函数 表示放行
  //next() 放行 next('/login') 强制跳转
  if(to.path==='/login') return next();//如果访问的是登录页那么直接放行
  //获取token 根据是否有token值来判断知否已经登录
  const tokenStr=window.sessionStorage.getItem('token')
  //如果没有token则强制跳转登录页
  if(!tokenStr) return next('/login')
  next()
})

export default router
  • 退出功能的实现

Home.vue

<template>
    <div>
       <el-button type="info" @click="logout">退出</el-button>
    </div>
</template>
<script>
export default {
    methods:{
        logout(){
            //清空sessionStorage
            window.sessionStorage.clear()
            //重定向到登录页
            this.$router.push("/login")
        }
    }
}
</script>
  • 优化element.js导入组件的代码
import { Button,Form,FormItem,Message,Input} from 'element-ui'

Vue.use(Button)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Input)

Vue.prototype.$message=Message
  • 将代码添加到github中

首先git status查看状态

element的步骤条修改样式_vue.js_47


将其全部添加到暂存区

element的步骤条修改样式_html5_48


再次查看状态

element的步骤条修改样式_vue.js_49


将代码全部提交到本地仓库中

element的步骤条修改样式_javascript_50


查看当前分支

element的步骤条修改样式_html5_51


说明当前提交的都在login分支下面切换到主分支 让主分支合并侧分支

将代码编辑器和所有占用改文件的程序关闭不然 之后在切换分支 否则回导致没有权限而出现致命错误导致源文件全部丢失

fatal: cannot create directory at ‘src/utils/animo’: Permission denied

element的步骤条修改样式_html5_52


合并login分支代码

element的步骤条修改样式_ico_53


推送到github

element的步骤条修改样式_vue.js_54


将本地分支推送到远程分支

首先切换到login分支

git checkout login

将远程仓库关联为orgin

element的步骤条修改样式_ico_55


推送至远程分支

element的步骤条修改样式_html5_56


element的步骤条修改样式_html5_57

实现主页功能

将分支切换到master主分支

  • 实现主页基本布局

使用element-ui的container布局
Home.vue

<template>
    <el-container class="home-container">
        <!-- 头部区域 -->
        <el-header >Header</el-header>
        <!-- 页面主体区域 -->
        <el-container>
            <el-aside width="200px">Aside</el-aside>
            <!-- 右侧内容主题 -->
            <el-main>Main</el-main>
        </el-container>
   </el-container>
</template>

element.js

import { 
        Button,
        Form,
        FormItem,
        Message,
        Input,
        Container,
        Header,
        Aside,
        Main
    } from 'element-ui'

Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
<style lang="less" scoped>
.home-container{
    height: 100%;
}
.el-header{
    background-color: #373d41;
}
.el-aside{
    background-color: #333744;
}
.el-main{
    background-color: #eaedf1;
}
</style>

element的步骤条修改样式_ico_58

  • 美化主页的header区域
.el-header{
    background-color: #373d41;
    display: flex;
    justify-content: space-between;
    padding-left: 0;
    align-items: center;
    color: #fff;
    div{
        display: flex;
        align-items: center;
        span{
            margin-left: 15px;
            font-size: 16px;
        }
    }
}

element的步骤条修改样式_vue.js_59

  • 实现侧边栏菜单区域

使用element-ui的侧边栏组件 并修改代码
Home.vue

<!-- 页面主体区域 -->
        <el-container>
            <el-aside width="200px">
                <!-- 侧边栏菜单区域 -->
                <el-menu
                    background-color="#333744"
                    text-color="#fff"
                    active-text-color="#ffd04b">
                    <!-- 一级菜单 -->
                    <el-submenu index="1">
                        <!-- 一级菜单模板区域 -->
                        <template slot="title">
                            <!-- 图标 -->
                            <i class="el-icon-location"></i>
                            <span>导航一</span>
                        </template>
                        
                        <!-- 二级菜单 -->
                        <el-menu-item index="1-4-1">
                            <template slot="title">
                            <!--二级菜单同样要有 图标和文本 -->
                            <i class="el-icon-location"></i>
                            <span>导航一</span>
                        </template>
                        </el-menu-item>
                    </el-submenu> 
                </el-menu>
            </el-aside>

注册组件

Menu,
        Submenu,
        // MenuItemGroup,
        MenuItem

    } from 'element-ui'

	Vue.use( Menu)
	Vue.use( Submenu)
	// Vue.use(MenuItemGroup)
	Vue.use(MenuItem)

element的步骤条修改样式_element的步骤条修改样式_60

  • 通过接口获取菜单数据

根据api文档提示 在登录之后进入主页所调用的api接口 都是需要授权的API

element的步骤条修改样式_ico_61


需要axios请求拦截器添加tokan,保证拥有获取数据的权限

如何添加:

  1. 调用axios的interceptors属性 该属性中有一个request成员 是一个请求拦截器,通过use为请求拦截器挂载一个回调函数(只要通过axios向服务器请求了数据 必然会通过use优先调用回调函数 对该请求进行预处理 return config表示 已经对请求头做了一次预处理)只有数据通过上述处理后 才会到达服务器进行下一步处理

main.js
在挂载axios之前首先设置拦截器

//设置拦截器
axios.interceptors.request.use(config=>{
  console.log(config)
  //在最后必须return config
  return config
})
Vue.prototype.$http=axios

element的步骤条修改样式_vue.js_62


此时header中并没有挂载Authorization字段 要为请求对象 添加token验证的Authorization字段 字段的值为曾今保存在seeionStorage的token值

axios.interceptors.request.use(config=>{  

config.headers.Authorization=window.sessionStorage.getItem('token')
  
  return config
})

验证是否设置成功

点击登录后 查看network

element的步骤条修改样式_ico_63


该请求头下有Authorization字段 说明设置成功

但由于我们发起的是登录请求 登录期间服务器并没有颁发令牌

此时Authorization的值为null,如果登录之后在调用其他接口 再次监听该属性的值 那么就不是null了 而是真正的一个token令牌

这样的话服务器在接收这个请求时就会判断 Authorization是否符合要求 如果符合要求才会去响应 否则则会驳回这次的响应

  • 获取左侧菜单的数据

Home.vue

data(){
        return{
            //左侧菜单数据
            menuList:[]
        }
    },
created(){
        this.getMenuList()
    },
//获取所有的菜单
        async getMenuList(){
            const{data:res}=await this.$http.get('menus')
            if(res.meta.status!==200) return this.$meeage.error(res.meta.msg)
            this.menuList=res.data
            
        }
  • 通过双层for循环渲染左侧菜单

根据返回的数据可知有两重菜单其中children项为子菜单

<el-submenu :index="item.id+''" v-for="item in menuList" :key="item.id">
                        <!-- 一级菜单模板区域 -->
                        <template slot="title">
                            <!-- 图标 -->
                            <i class="el-icon-location"></i>
                            <span>{{item.authName}}</span>
                        </template>

                        <!-- 二级菜单 -->
                        <el-menu-item :index="+''" v-for="subItem in item.children" :key="">
                            <template slot="title">
                            <!--二级菜单同样要有 图标和文本 -->
                            <i class="el-icon-location"></i>
                            <span>{{subItem.authName}}</span>
                        </template>
                        </el-menu-item>
                    </el-submenu>

element的步骤条修改样式_element的步骤条修改样式_64

  • 为选中项设置字体颜色并添加分类图标

为选中项添加高亮

<el-menu
                    background-color="#333744"
                    text-color="#fff"
                    active-text-color="#409EFF">

为二级菜单添加统一图标

<!-- 二级菜单 -->
                        <el-menu-item :index="+''" v-for="subItem in item.children" :key="">
                            <template slot="title">
                            <!--二级菜单同样要有 图标和文本 -->
                            <i class="el-icon-menu"></i>
                            <span>{{subItem.authName}}</span>
                        </template>
                        </el-menu-item>

由于一级菜单需要分别添加不同的图标 首先定义一个图标对象
将每一个菜单的id值作为键值 值为第三方图标库的类名

//字体图标对象
            iconsObj:{
                '125':'iconfont icon-users',
                '103':'iconfont icon-tijikongjian',
                '101':'iconfont icon-shangpin',
                '102':'iconfont icon-danju',
                '145':'iconfont icon-baobiao'
            }

动态绑定一级菜单的图标值

<el-submenu :index="item.id+''" v-for="item in menuList" :key="item.id">
                        <!-- 一级菜单模板区域 -->
                        <template slot="title">
                            <!-- 图标 -->
                            <i :class="iconsObj[item.id]"></i>
                            <span>{{item.authName}}</span>
                        </template>

设置图标到标题的距离

.iconfont{ margin-right:10px ; }

element的步骤条修改样式_html5_65

  • 每次只能打开一个菜单项 并解决边框问题

根据element-ui的文档加上 unique-opened

<el-menu
                    background-color="#333744"
                    text-color="#fff"
                    active-text-color="#409EFF" unique-opened>

去除边框

.el-menu{
        border-right: none;
    }
  • 实现侧边栏的折叠与展开效果

在侧边栏菜单区域添加一个按钮给按钮绑定一个点击事件 控制菜单是否展开

<!-- 侧边栏菜单区域 -->
                <div class="toggle-button" @click="toggleCollapse">
                    |||
                </div>
                <el-menu

设置样式

.toggle-button{
    background-color: #4a5064;
    font-size: 10px;
    line-height: 24px;
    color: #fff;
    text-align: center;
    letter-spacing: 0.2em;//线之间的距离
    cursor: pointer;

}

根据element-ui api 给el-menu添加collapse的属性 该属性是布尔值 此时是动态绑定

<el-menu
                    background-color="#333744"
                    text-color="#fff"
                    active-text-color="#409EFF" 
                    unique-opened 
                    :collapse="isCollapse"

在data中添加 isCollapse值 默认不展开 为false

//是否折叠展示
            isCollapse:false

定义点击按钮控制菜单展开的事件

//点击顶部按钮 切换菜单折叠与展开
        toggleCollapse(){
            this.isCollapse=!this.isCollapse
        }

添加collapse-transiton属性 去除菜单折叠的动画

<el-menu
                    background-color="#333744"
                    text-color="#fff"
                    active-text-color="#409EFF" 
                    unique-opened 
                    :collapse="isCollapse"
                    :collapse-transition="false"

根据菜单栏是否展开来动态设置侧边栏的宽度

<el-container>
            <el-aside :width="isCollapse?'64px':'200px'">

element的步骤条修改样式_element的步骤条修改样式_66

  • 实现首页路由重定向到子组件Welcome的效果

创建Welcome子组件
实现home路由重定向 并设置对应的子组件的路由
index.js

{path:"/home",
      component:home,
      // 重定向到所嵌套的子组件
      redirect:'/Welcome',
      children:[{path:'/welcome',component:welcome}]  }

在首页的内容主体区域放置该子组件的占位符

<!-- 右侧内容主题 -->
            <el-main>
                <!-- 放置子组件路由占位符 -->
                <router-view></router-view>
            </el-main>

这样已进入主页 就会默认显示Welcome子组件

element的步骤条修改样式_vue.js_67

  • 实现侧边栏路由链接改造

根据element-ui提供的菜单api 添加router属性 为侧边栏开启路由模式

<el-menu
                    background-color="#333744"
                    text-color="#fff"
                    active-text-color="#409EFF" 
                    unique-opened 
                    :collapse="isCollapse"
                    :collapse-transition="false"
                    :router="true"
                    >

给二级菜单的index绑定subItem的path作为路径的值 由于后端提供的path前没有’/’,要为其添加

<!-- 二级菜单 -->
                        <el-menu-item :index="'/'+subItem.path" v-for="subItem in item.children" :key="">

element的步骤条修改样式_ico_68

用户列表

新建用户列表组件

创建路由关系

import users from '../components/user/Users.vue'
children:[
        {path:'/welcome',component:welcome},
        {path:'/users',component:users}
      ] }
  • 保存左侧菜单栏被点击后的激活(某一项的展开和高亮)状态

在data中定义被激活的链接地址
Home.vue

activePath:''

在点击时将每一个状态值保存到seesionStorage和data中

<el-menu-item :index="'/'+subItem.path" v-for="subItem in item.children" :key=""
                         @click="saveNavState('/'+subItem.path)">
//点击按钮 保存菜单的状态值
        saveNavState(activePath){
            window.sessionStorage.setItem('activePath',activePath)
            this.activePath=activePath
        }

根据element-ui提供的api 给el-menu添加default-active属性 动态绑定activePath的值 实现点击某一项 能够高亮显示

<el-menu
                    background-color="#333744"
                    text-color="#fff"
                    active-text-color="#409EFF" 
                    unique-opened 
                    :collapse="isCollapse"
                    :collapse-transition="false"
                    :router="true"
                    :default-active="activePath"
                    >

在created中给activePath赋值使其一打开页面就获取菜单的状态并渲染

this.activePath=window.sessionStorage.getItem('activePath')
  • 绘制用户列表的布局

使用element-ui提供的面包屑导航和卡片视图
Users.vue

<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 >
            <!-- 搜索与添加区域 -->     
            <!-- gutter组件中的间隙 -->
            <el-row :gutter="20">
                <!-- 使用elememt的栅格 span指定宽度 -->
                <el-col :span="9">
                    <el-input placeholder="请输入内容">
                        <el-button slot="append" icon="el-icon-search"></el-button>
                    </el-input>
                </el-col>
                <el-col :span="4">
                    <el-button type="primary">添加用户</el-button>
                </el-col>
            </el-row>
        </el-card>
    </div>
</template>
Breadcrumb,
        BreadcrumbItem,
        Card,
        Col,
        Row
    } from 'element-ui'
Vue.use( Breadcrumb)
Vue.use( BreadcrumbItem)
Vue.use(Card)
Vue.use(Col)
Vue.use(Row)

global.css

.el-breadcrumb{
    margin-bottom: 15px;
    margin-right: 15px;
}
.el-card{
    box-shadow: 0 1px 1px rgba(0,0,0,0.15) !important;
}

element的步骤条修改样式_element的步骤条修改样式_69

  • 获取用户列表数据
data(){
        return{
            //获取用户列表的参数对象
            queryInfo:{
                query:'',
                pagenum:1,
                pagesize:2
            },
            userList:[],

            total:0
        }
    },
    created(){
        this.getUserList()
    },
    methods:{
        async getUserList(){
           const {data:res}=await this.$http.get('users',{params:this.queryInfo})
           if(res.meta.status!==200){
               return this.$message.error('获取用户列表失败')
           }
           this.userList=res.data.users
           this.total=res.data.total
        }
    }
  • 使用el-tabel组件渲染基本的用户列表
<!-- 用户列表区域 -->
            <!-- data指定表格数据源 stripe实现隔行变色-->         
            <el-table :data="userList" border stripe>
                 <!-- label表格标题 prop对应的值-->
                <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="状态" prop="mg_state"></el-table-column>
                <el-table-column label="操作"></el-table-column>
            </el-table>

global.css

.el-table{
    margin-top: 15px;
    font-size: 12px;
}

解决表头边框和内容边框不对其的问题
global.css

body .el-table th.gutter{
    display: table-cell!important;
}

添加索引列
设置type为index就行

<el-table :data="userList" border stripe>
                <!-- 索引列 -->
                 <el-table-column type="index"></el-table-column>
  • 使用作用域插槽来实现状态列显示效果

使用作用域插槽 通过slot-scope接收作用域数据 将mg_state的布尔值 渲染成一个开关状态 由于slot-scope的值会覆盖prop的值此时可以将prop属性删除
通过scope.row可以获取这一行的数据 因此在switch组件中可以使用v-model绑定该行数据的开关状态的值

<el-table-column label="状态">
                    <template slot-scope="scope">
                        <!-- 这一行的所有数据 -->
                        <!-- {{scope.row}} -->
                        <el-switch v-model="scope.row.mg_state"></el-switch>
                    </template>
                </el-table-column>

element的步骤条修改样式_ico_70

  • 通过作用域渲染操作列
<el-table-column label="操作" width="180" >
                    <template>
                        <!-- 修改 -->
                       <el-button type="primary" size="mini" icon="el-icon-edit"></el-button>
                       <!-- 删除 -->
                       <el-button type="danger" size="mini" icon="el-icon-delete"></el-button>
                       <!-- 分配角色 -->
                       <el-tooltip class="item" effect="dark" content="分配角色" placement="top">
                           <!-- 鼠标放置有文字提示 enterable使鼠标离开后隐藏-->
                          <el-button type="warning" :enterable="false" size="mini" icon="el-icon-setting"></el-button>
                       </el-tooltip>        
                    </template>
                </el-table-column>

element的步骤条修改样式_ico_71

  • 实现分页效果

使用element-ui的分页组件并修改

<!-- 分页区域 
                 current-page 当前的页码
                 page-sizes 可以调整每一页的数据条数
                 page-size 当前每页显示多少条数据
                 layout指定页面上显示的布局结构-->
             <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current-page="queryInfo.pagenum"
                :page-sizes="[1, 2, 5,10]"
                :page-size="queryInfo.pagesize"
                layout="total, sizes, prev, pager, next, jumper"
                :total="total">
            </el-pagination>

定义handleSizeChange,handleCurrentChange事件

//监听pageSize改变的事件
        handleSizeChange(newSize){
            this.queryInfo.pagesize=newSize
            this.getUserList()
        },

        //监听页码值改变的事件
        handleCurrentChange(newPage){
            this.queryInfo.pagenum=newPage
            this.getUserList()
        }

调整分页的样式
global.css

.el-pagination{
    margin-top: 15px;
}

element的步骤条修改样式_element的步骤条修改样式_72


element的步骤条修改样式_html5_73

  • 修改用户状态

当点击用户状态的开关按钮时 要将该状态同步保存到数据库中(不然刷新页面又回到了之前的状态)
根据element-ui提供的api 使用change监听开关状态的改变

v-model值双向数据绑定了scope.row.mg_state 当开关状态发生改变时 也会将数据同步到scope.row.mg_state上 所以要将该值作为参数传递

<el-switch v-model="scope.row.mg_state" @change="userstateChanged(scope.row)"></el-switch>

根据api文档 修改用户列表要用put请求

//监听switch开关状态的改变
        async userstateChanged(userinfo){
            const {data:res}=await this.$http.put(`users/${userinfo.id}/state/${userinfo.mg_state}`)
            if(res.meta.status!==200){
                //更新数据失败 需要将开关状态恢复到之前的样子
                userinfo=!userinfo.mg_state
                return this.$message.error('更新用户信息失败')
            }
            this.$message.success('更新状态成功')
        }

element的步骤条修改样式_html5_74

  • 实现搜索功能

v-model双向数据绑定queryInfo.query 并给搜索按钮绑定事件使其重新调用getUserList渲染数据

根据api 添加clearable属性 实现清空功能 并定义clear事件 使其清空后数据能够显示回之前的状态

<el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="getUserList">
                        <el-button slot="append" icon="el-icon-search"  @click="getUserList"></el-button>
                    </el-input>

element的步骤条修改样式_ico_75


element的步骤条修改样式_ico_76

添加用户

  • 渲染添加用户对话框
</el-card>

        <el-dialog
        title="提示"
        :visible.sync="dialogVisible"
        width="50%"
        >
            <span>这是一段信息</span>
            <span slot="footer" class="dialog-footer">
                <el-button @click="dialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
            </span>
        </el-dialog>

在data中定义dialogVisible

dialogVisible:false

给添加用户按钮绑定弹框事件

<el-button type="primary"  @click="dialogVisible = true">添加用户</el-button>

element的步骤条修改样式_html5_77

  • 渲染添加用户表单
<!-- 添加用户对话框 -->
        <el-dialog
        title="添加用户"
        :visible.sync="dialogVisible"
        width="50%"
        >
            <!-- 内容主体 -->
            <el-form ref="form" :model="addform" label-width="80px" :rules="addFormRules">
                <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="dialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
            </span>
        </el-dialog>
//添加用户的表单数据
            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: 7, max: 12, message: '长度在 7 到 12 个字符', trigger: 'blur' }
                ],
                email:[
                    { required: true, message: '请输入邮箱地址', trigger: 'blur' },
                    { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
                ],
               mobile:[
                     { required: true, message: '请输入手机号', trigger: 'blur' },
                    { min: 9, max: 12, message: '长度在 7 到 12 个数字', trigger: 'blur' }
                ]
            }

element的步骤条修改样式_vue.js_78

  • 自定义邮箱和手机号的校验规则

根据element-ui的自定义规则
首先定义邮箱和手机号的规则

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

        //验证手机号的规则
        var checkMobile=(rule,value,cb)=>{
            const regMobile=/^(0|86|17951)?(13[0-9]|15[0123456789]|17[678]18[0-9]|14[57])[0-9]{8}$/
            if(regMobile.test(value)){
                return cb()
            }
            cb(new Error('请输入合法手机号'))
        }

在表单验证规则中添加自定义的邮箱和手机号规则

//表单验证规则
            addFormRules:{
                username:[
                     { required: true, message: '请输入用户名称', trigger: 'blur' },
                    { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
                ],
                password:[
                    { required: true, message: '请输入密码', trigger: 'blur' },
                    { min: 7, max: 12, message: '长度在 7 到 12 个字符', trigger: 'blur' }
                ],
                email:[
                    { required: true, message: '请输入邮箱地址', trigger: 'blur' },
                    { validator:checkEmail,  trigger: ['blur', 'change'] }
                ],
                mobile:[
                     { required: true, message: '请输入手机号', trigger: 'blur' },
                    {  validator:checkMobile,trigger: 'blur' }
                ]
            }
  • 实现关闭对话框表单的重置

绑定close事件

<el-dialog
        title="添加用户"
        :visible.sync="dialogVisible"
        width="50%"
        @close="addDialogClose"
        >
//监听添加用户对话剧的关闭事件
        addDialogClose(){
            this.$refs.form.resetFields();
        }
  • 点击确定按钮对表单进行预验证

给确定按钮绑定click事件

<el-button type="primary" @click="addUsers">确 定</el-button>

根据element-ui提供的api定义该预验证的方法

addUsers(){
         this.$refs.form.validate((valid) => {
          if (valid) {
            alert('submit!');
          } else {
            console.log('error submit!!');
            return false;
          }
        });
        }

element的步骤条修改样式_javascript_79

  • 调用api接口完成添加用户

由于data中的addform数据和调用接口时传递的参数一致 所以我们可以直接将addform作为传递参数的对象

//添加用户的表单数据
            addform:{
                username:'',
                password:'',
                email:'',
                mobile:''
            },

注意:该api的状态码为201

//点击确定按钮对表单进行预验证
        addUsers(){
         this.$refs.form.validate(async valid => {
            if(!valid) return

            //可以发起网络请求
            const {data:res}=await this.$http.post('users',this.addform)
            if(res.meta.status!==201){
                return this.$message.error('添加用户失败')
            }

            this.$message.success('添加用户成功')
            //隐藏添加用户的对话框
            this.dialogVisible=false
            //重新渲染列表
            this.getUserList()
        });
        }

修改用户

点击按钮实现修改用户的功能

给修改按钮绑定点击事件

<!-- 修改 -->
                       <el-button @click="showEditDialog"  type="primary" size="mini" icon="el-icon-edit"></el-button>
<!-- 修改用户的对话框 -->
        <el-dialog
            title="修改用户"
            :visible.sync="editDiologVisible"
            width="50%"
           >
            <span>这是一段信息</span>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editDiologVisible = false">取 消</el-button>
                <el-button type="primary" @click="editDiologVisible = false">确 定</el-button>
            </span>
        </el-dialog>

data中定义dialogVisible为false

dialogVisible:false,

弹框方法

// 展示编辑修改对话框
        showEditDialog(){
            this.editDiologVisible=true
        }
  • 根据修改用户的id值查询用户的信息

在修改的方法中传递id值

<!-- 修改 -->
                       <el-button @click="showEditDialog()"  type="primary" size="mini" icon="el-icon-edit"></el-button>

在data中定义查询到的信息对象

//查询到的用户信息对象
             editForm:{},

调用api接口 获取id值对应的用户信息

// 展示编辑修改对话框
        async showEditDialog(id){
            const {data:res}=await this.$http.get('users/'+id)
            if(res.meta.status!==200){
                return this.$message.error('获取用户数据失败')
            }
            this.editForm=res.data
            this.editDiologVisible=true
        }
  • 渲染修改用户的表单

给活动名称添加disable属性 使其不可修改

<el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="活动名称" >
                    <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>
//修改用户 弹框表单的校验规则
            editFormRules:{
                email:[
                    { required: true, message: '请输入邮箱地址', trigger: 'blur' },
                    { validator:checkEmail,  trigger: ['blur', 'change'] }
                ],
                 mobile:[
                     { required: true, message: '请输入手机号', trigger: 'blur' },
                    {  validator:checkMobile,trigger: 'blur' }
                ]
            }

element的步骤条修改样式_javascript_80

  • 关闭后表单重置
<!-- 修改用户的对话框 -->
        <el-dialog
            title="修改用户"
            :visible.sync="editDiologVisible"
            width="50%"
            @close="editDialogClose"
           >
editDialogClose(){
        this.$refs.editFormRef.resetFields()
      }
  • 点击确定按钮完成表单预验证并完成修改数据
<el-button type="primary" @click="editUserInfo">确 定</el-button>
//修改用户信息并提交
       editUserInfo(){
           this.$refs.editFormRef.validate(async valid=>{
               if(!valid) return
               
               //发起修改用户数据请求
              const {data:res}=await this.$http.put('users/'+,{email:this.editForm.email,mobile:this.editForm.mobile})
              if(res.meta.status!==200){
                  return this.$message.error('修改用户数据失败')
              }
            
              //关闭对话框
              this.editDiologVisible=false
              //刷新数据列表
              this.getUserList()
              //提示修改成功
              this.$message.success('更新用户信息成功')
           })
       }

element的步骤条修改样式_element的步骤条修改样式_81


element的步骤条修改样式_element的步骤条修改样式_82


element的步骤条修改样式_vue.js_83

删除用户

  • 点击删除弹框询问是否删除

给删除按钮绑定事件 并传递对应的id值

<el-button @click="removeUserById()" type="danger" size="mini" icon="el-icon-delete"></el-button>

使用element-ui的messagebox弹框组件
在element.js下注册该组件

MessageBox
    } from 'element-ui'
Vue.prototype.$confirm=MessageBox.confirm

定义删除用户信息的方法

//根据id删除用户的信息
      async removeUserById(id){
           //弹框询问是否删除数据
           //该方法的返回值是一个promise 所以要用await async简化其操作
           const confirmResult=await this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            })
            .catch(err=>{
                return err
            })
            //如果用户确认删除 则返回的字符串为confim 
            //如果点击取消 则用catch捕获错误消息 并return出去 这样才不会报错 其返回的字符串为cancel
            // console.log(confirmResult)
            //如果confirmResult不为confim 说明用户不想删除则弹框取消从删除
            if(confirmResult!=='confirm'){
                return this.$message.info('已取消删除')
            }

            console.log('确认了删除')
       }

element的步骤条修改样式_html5_84


element的步骤条修改样式_element的步骤条修改样式_85

  • 完成删除用户
//根据id删除用户的信息
      async removeUserById(id){
           //弹框询问是否删除数据
           //该方法的返回值是一个promise 所以要用await async简化其操作
           const confirmResult=await this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            })
            .catch(err=>{
                return err
            })
            //如果用户确认删除 则返回的字符串为confim 
            //如果点击取消 则用catch捕获错误消息 并return出去 这样才不会报错 其返回的字符串为cancel
            // console.log(confirmResult)
            //如果confirmResult不为confim 说明用户不想删除则弹框取消从删除
            if(confirmResult!=='confirm'){
                return this.$message.info('已取消删除')
            }

           const {data:res}=await this.$http.delete('users/'+id)
           if(res.meta.status!==200){
               return this.$message.error('删除用户失败')
           }
           this.$message.success('删除用户成功')
           this.getUserList()
       }
  • 创建user子分支并将代码推送到github

查看当前分支

element的步骤条修改样式_ico_86


新建一个user分支并切换到该分支

element的步骤条修改样式_javascript_87


接着查看分支我们可以发现切换到了user分支 此时所有的修改的代码都在user分支

element的步骤条修改样式_ico_88


查看user分支下的代码状态

element的步骤条修改样式_javascript_89


提交到暂存区 此时所有的代码都已经提交到了user子分支

element的步骤条修改样式_element的步骤条修改样式_90


查看状态

element的步骤条修改样式_html5_91


将本地新增的user分支推送到远程github

将远程仓库关联为orgin

element的步骤条修改样式_vue.js_92


由于是第一次推送该分支 所以要运行以下命令 表示将本地分支推送到远程orgin仓库中 同时以user分支进行保存

element的步骤条修改样式_html5_93


element的步骤条修改样式_element的步骤条修改样式_94


将所有代码合并到master主分支首先先切换到master主分支

element的步骤条修改样式_javascript_95


让主分支合并侧分支 此时master的代码也是最新的了

element的步骤条修改样式_element的步骤条修改样式_96


将本地master分支更新到远程

git push

如果出现报错则尝试$ git push --set-upstream origin master命令

element的步骤条修改样式_javascript_97


此时github主分支也得以更新

element的步骤条修改样式_javascript_98

权限列表

查看当前所在分支

element的步骤条修改样式_element的步骤条修改样式_99


新建一个rights子分支

element的步骤条修改样式_vue.js_100


此时已经在rights子分支推送到远程分支

element的步骤条修改样式_html5_101


接下来都在rights分支经行开发

  • 创建权限列表组件 设置对应的路由关系

新建power文件夹 在其中新建Rights.vue

设置对应的路由关系

import rights from '../components/power/Rights.vue'
children:[
        {path:'/welcome',component:welcome},
        {path:'/users',component:users},
        {path:'/rights',component:rights}
      ] }

rights.vue

<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-card>
    </div>
</template>
<script>
export default {
    data(){
        return{
            
        }
    }
}
</script>
<style lang="less" scoped>

</style>

element的步骤条修改样式_vue.js_102

  • 获取数据渲染权限列表
export default {
    data(){
        return{
            //权限列表
            rightsList:[]
        }
    },
    created(){
        this.getRightsList()
    },
    methods:{
         //获取所有的权限
       async getRightsList() {
         const{data:res}=await this.$http.get('rights/list')
         if(res.meta.status!==200){
            return this.$message.error('获取权限列表失败')
         }
         this.rightsList=res.data
         console.log( this.rightsList+'权限')
        }
    }
}
<el-card>
            <el-table :data="rightsList" stripe border >
                <el-table-column type="index"></el-table-column>
                <el-table-column label="权限名称" prop="authName"></el-table-column>
                <el-table-column label="路径" prop="path"></el-table-column>
                <el-table-column label="权限等级" prop="level">
                    <!-- 使用element 的 tag组件 -->
                    <template slot-scope="scope">
                        <!-- 使用v-if来按需显示 权限值所对应的哪一个 标签 -->
                        <el-tag v-if="scope.row.level==='0'">一级</el-tag>
                        <el-tag v-else-if="scope.row.level==='1'" type="success">二级</el-tag>
                        <el-tag v-else type="warning">三级</el-tag>
                    </template>
                </el-table-column>
            </el-table>
        </el-card>

element的步骤条修改样式_vue.js_103

角色列表

  • 用户-角色-权限三者关系

element的步骤条修改样式_ico_104


在power下新建Roles.vue 创建对应的路由关系

绘制页面基础布局

<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="primary">添加角色</el-button>
                </el-col>
            </el-row>
        </el-card>
    </div>
</template>

获取角色列表数据

export default {
    data(){
        return{
            //所有角色列表数据
            roleList:[]
        }
    },
    created(){
        this.getRolesList()
    },
    methods:{
      async getRolesList(){
           const {data:res}=await this.$http.get('roles')
           if(res.meta.status!==200){
               return this.$message.console.error('获取角色列表失败');
           }
           this.roleList=res.data
           console.log(this.roleList)
        }
    }
}
  • 渲染角色列表区域
<!-- 角色列表区域 -->
            <el-table :data="roleList" border stripe>
                <!-- 展开列 -->
                <el-table-column type="expand"></el-table-column>
                <!-- 索引列 -->
                <el-table-column type="index"></el-table-column>
                <el-table-column label="角色名称" prop="roleName"></el-table-column>
                <el-table-column label="角色描述" prop="roleDesc"></el-table-column>
                 <el-table-column label="操作" width="300px">
                     <template >
                          <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
                         <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
                         <el-button size="mini" type="warning" icon="el-icon-setting">分配权限</el-button>
                     </template>
                 </el-table-column>
            </el-table>

element的步骤条修改样式_html5_105

  • 完成添加角色功能
<el-button type="primary" @click="addRoles">添加角色</el-button>
<!-- 添加角色对话框 -->
        <el-dialog
            title="添加角色"
            :visible.sync="addRolesVisible"
            width="50%"
            @close="resetForm"
          >
            <el-form :model="addRolesForm" :rules="editRolesRules" ref="ruleFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="角色名称" prop="name">
                    <el-input v-model="addRolesForm.name"></el-input>
                </el-form-item>
                <el-form-item label="角色描述" prop="miaoShu">
                    <el-input v-model="addRolesForm.miaoShu"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="resetForm">取 消</el-button>
                <el-button type="primary" @click="submitForm">确 定</el-button>
            </span>
        </el-dialog>
addRolesVisible:false,
            addRolesForm:{
                name:'',
                miaoShu:''
            },
            addRolesRules: {
                name: [
                    { required: true, message: '请输入角色名称', trigger: 'blur' },
                    { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
                ],
                miaoShu:[    
                    { required: true, message: '请输入角色描述', trigger: 'blur' },
                    {  max: 20, message: '长度在20个字符之内', trigger: 'blur' }
                ]
             },
// 弹出添加角色对话框
        addRoles(){
            this.addRolesVisible=true
        },
        // 重置添加角色对话框
        resetForm() {
         this.addRolesVisible=false
         this.$refs.ruleFormRef.resetFields();
        },
        //提交添加角色
        submitForm() {
            this.$refs.ruleFormRef.validate(async valid => {
            if (valid) {
                 this.addRolesVisible=false
                 const {data:res}=await this.$http.post('roles',
                 {roleName:this.addRolesForm.name,
                 roleDesc:this.addRolesForm.miaoShu},
                 
                )
                if(res.meta.status!==201){
                      console.log(res)
                    return this.$message.error('添加角色失败')
                }
                this.$message.success('添加角色成功')
                this.getRolesList()

            } else {
                console.log('error submit!!');
                return false;
            }
         });
        },
  • 编辑角色
<el-button size="mini" type="primary" icon="el-icon-edit" @click="editRoles()">编辑</el-button>
<!-- 修改角色 -->
        <el-dialog
            title="修改角色"
            :visible.sync="editRolesVisible"
            width="50%"
            @close="editResetForm"
          >
            <el-form :model="editRolesForm" :rules="addRolesRules" ref="editRuleFormRef" label-width="100px" class="demo-ruleForm">
                <el-form-item label="角色名称" prop="name">
                    <el-input v-model="editRolesForm.name"></el-input>
                </el-form-item>
                <el-form-item label="角色描述" prop="miaoShu">
                    <el-input v-model="editRolesForm.miaoShu"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="editResetForm">取 消</el-button>
                <el-button type="primary" @click="editSubmitForm">确 定</el-button>
            </span>
        </el-dialog>
editRolesRules: {
                name: [
                    { required: true, message: '请输入角色名称', trigger: 'blur' },
                    { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
                ],
                miaoShu:[    
                    { required: true, message: '请输入角色描述', trigger: 'blur' },
                    {  max: 20, message: '长度在20个字符之内', trigger: 'blur' }
                ]
             },
            editRolesVisible:false,
            // 所编辑的角色信息
            editRolesForm:{
                id:'',
                name:'',
                miaoShu:''
            },
// 获取编辑角色信息
       async editRoles(id){
            this.editRolesVisible=true
            const {data:res}=await this.$http.get('roles/'+id)
            if(res.meta.status!==200){
                console.log(res)
                return this.$message.error("获取角色信息失败")
            }
            this.editRolesForm.id=res.data.roleId
            this.editRolesForm.name=res.data.roleName
            this.editRolesForm.miaoShu=res.data.roleDesc
        },
        // 关闭 并 重置编辑角色表单
        editResetForm(){
            this.editRolesVisible=false
            this.$refs.editRuleFormRef.resetFields();
        },
        // 提交编辑角色内容
        editSubmitForm(){
             this.$refs.editRuleFormRef.validate(async valid => {
            if (valid) {
                 this.editRolesVisible=false
                 const {data:res}=await this.$http.put('roles/'+this.editRolesForm.id,
                 {roleName:this.editRolesForm.name,
                 roleDesc:this.editRolesForm.miaoShu},
                 
                )
                if(res.meta.status!==200){
                      console.log(res)
                    return this.$message.error('编辑角色失败')
                }
                this.$message.success('编辑角色成功')
                this.getRolesList()

            } else {
                console.log('error submit!!');
                return false;
            }
         });
        },
  • 删除角色
<el-button size="mini" type="danger" icon="el-icon-delete" @click="delRoles()">删除</el-button>
// 删除角色
       async delRoles(id){
         const confirmMes=await this.$confirm('此操作将永久删除该角色, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).catch(err=>{
            return err
        })
        // console.log(confirmMes)
        if(confirmMes!=='confirm'){
            return this.$message.info('用户取消了该操作')
        }
        const {data:res}=await this.$http.delete('roles/'+id)
        if(res.meta.status!==200){
             return this.$message.error('删除失败')
        }
        this.$message.success('删除成功')
        this.getRolesList()
      }
  • 渲染每一个角色下的所有权限

首先在展开列中通过作用域插槽来渲染每一个角色下所有的权限数据 通过pre来将权限数据格式化

<!-- 角色列表区域 -->
            <el-table :data="roleList" border stripe>
                <!-- 展开列 -->
                <el-table-column type="expand">
                    <template slot-scope="scope">
                        <pre>{{scope.row.children}}</pre>
                    </template>
                </el-table-column>

element的步骤条修改样式_javascript_106


其中第一层children值代表第一层权限 后面两层分别代表第二,三层权限

嵌套for循环渲染这三个层次的权限

<!-- 角色列表区域 -->
            <el-table :data="roleList" border stripe>
                <!-- 展开列 -->
                <el-table-column type="expand">
                    <template slot-scope="scope">
                        <!-- 栅格化分别渲染不同级别权限列 -->
                        <el-row :class="['bdbottom',i1===0?'bdtop':'','vcenter']" v-for="(item1,i1) in scope.row.children" :key="">
                            <!-- 渲染第一级权限 -->
                            <el-col :span="5">
                                <el-tag>{{item1.authName}}</el-tag>
                                <i class="el-icon-caret-right"></i>
                            </el-col>
                            <!-- 渲染第二级 -->
                             <el-col :span="19" >
                                 <el-row :class="[i2===0?'':'bdtop','vcenter']" v-for="(item2,i2) in item1.children" :key="">
                                   <el-col :span="5">
                                       <el-tag type="success">{{item2.authName}}</el-tag>
                                        <i class="el-icon-caret-right"></i>
                                   </el-col>  
                                    <el-col :span="19">
                                        <!-- 第三级权限 -->
                                        <el-tag type="warning"  v-for="item3 in item2.children" :key="">{{item3.authName}}</el-tag>
                                         <i class="el-icon-caret-right"></i>
                                    </el-col> 
                                 </el-row>
                             
                            </el-col>
                           
                            
                        </el-row>
                    </template>
                </el-table-column>

对应样式

.el-tag{
    margin:7px
}
.bdtop{
    border-top: 1px solid #eee;
}
.bdbottom{
    border-bottom: 1px solid #eee;
}
.vcenter{
    display: flex;
    align-items: center;
}

设置最小宽度
.global.css

html,body,#app{
    height: 100%;
    margin: 0;
    padding: 0;
    min-width: 1366px;
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200710171827674.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTg2MDYwOQ==,size_16,color_FFFFFF,t_70)

 - **删除角色下指定权限**


为标签绑定closable属性 使其变为可删除的标签 绑定删除事件

```javascript
  <!-- 第三级权限 -->
                                        <el-tag type="warning"  v-for="item3 in item2.children" :key="" closable @close="removeRightById(scope.row,)">{{item3.authName}}</el-tag>

定义删除权限标签的事件

// 删除权限标签   
      async removeRightById(role,rightId){
        const confirm= await this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).catch(err=>err)
        if(confirm!=='confirm'){
            return this.$message.info('用户取消了操作')
        }

        const {data:res}= await this.$http.delete(`roles/${}/rights/${rightId}`)
        if(res.meta.status!==200){
            return this.$message.error('删除角色列表失败')
        }
        //为了防止调用getRolesList()会发生页面重新渲染 需要再一次手动
        //打开展开列 又由于返回的是完整的权限列表 只要重新将权限列表数据重新赋值
        role.children=res.data
      }

为第一级和第二级权限的标签也绑定该事件

<!-- 角色列表区域 -->
            <el-table :data="roleList" border stripe>
                <!-- 展开列 -->
                <el-table-column type="expand">
                    <template slot-scope="scope">
                        <!-- 栅格化分别渲染不同级别权限列 -->
                        <el-row :class="['bdbottom',i1===0?'bdtop':'','vcenter']" v-for="(item1,i1) in scope.row.children" :key="">
                            <!-- 渲染第一级权限 -->
                            <el-col :span="5">
                                <el-tag closable @close="removeRightById(scope.row,)">{{item1.authName}}</el-tag>
                                <i class="el-icon-caret-right"></i>
                            </el-col>
                            <!-- 渲染第二级 -->
                             <el-col :span="19" >
                                 <el-row :class="[i2===0?'':'bdtop','vcenter']" v-for="(item2,i2) in item1.children" :key="">
                                   <el-col :span="5">
                                       <el-tag type="success" closable @close="removeRightById(scope.row,)">{{item2.authName}}</el-tag>
                                        <i class="el-icon-caret-right"></i>
                                   </el-col>

element的步骤条修改样式_ico_107

  • 点击分配权限 展示分配权限对话框 并获取权限列表数据
<el-button size="mini" type="warning" icon="el-icon-setting" @click="showSetRightDialog">分配权限</el-button>
setRightDialogVisible:false,
            // 所有权限的数据
            rightslist:[],
// 展示分配权限的对话框
      async showSetRightDialog(){
          // 获取所有权限数据
          this.setRightDialogVisible=true
          // 由于渲染的是树状列表 所有根据api 路径中所携带的类型是tree   
          const {data:res}=await this.$http.get('rights/tree')
          if(res.meta.status!==200){
              return this.$message.error('获取权限数据失败')
          }
          this.rightslist=res.data
          console.log(this.rightslist)
      }

使用树形控件渲染

<!-- 分配权限 -->
        <el-dialog
            title="分配权限"
            :visible.sync="setRightDialogVisible"
            width="50%"
            >
            <!-- 树形控件 -->
            <el-tree :data="rightslist" :props="treeProps" ></el-tree>
            <span slot="footer" class="dialog-footer">
                <el-button @click="setRightDialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="setRightDialogVisible = false">确 定</el-button>
            </span>
            </el-dialog>
treeProps:{
                label:'authName',
                children:'children'
            }

element的步骤条修改样式_vue.js_108

在树状节点控件加node-key,default-expand-all属性
node-key:每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
default-expand-all:默认将所有的选项展开

<!-- 树形控件 -->
            <el-tree :data="rightslist"
             :props="treeProps" 
             show-checkbox node-key="id" 
             default-expand-all

element的步骤条修改样式_html5_109

  • 在点击分配角色时 自动将树状列表中角色已经拥有的权限勾选

根据api增加default-checked-keys属性 表示默认的勾选项

<!-- 树形控件 -->
            <el-tree :data="rightslist"
             :props="treeProps" 
             show-checkbox node-key="id" 
             default-expand-all
             :default-checked-keys="defKeys"></el-tree>

在data中定义该数组

defKeys:[]

通过递归的形式获取所有三级权限的id并保存到 defKeys数组中

getLeafKeys(node,arr){
          //如果当前node节点不包含children属性 则是三级节点
          if(!node.children){
             //注意:不要缺少return
              return arr.push(node.id)
          }

          node.children.forEach(item=>this.getLeafKeys(item,arr))
      }

往showSetRightDialog方法传递所对应的角色列表参数

<el-button size="mini" type="warning" icon="el-icon-setting" @click="showSetRightDialog(scope.row)">分配权限</el-button>

在showSetRightDialog方法中调用递归函数

// 展示分配权限的对话框
      async showSetRightDialog(role){
          //递归获取三级节点的id
          this.getLeafKeys(role,this.defKeys)

          // 获取所有权限数据
          this.setRightDialogVisible=true
          // 由于渲染的是树状列表 所有根据api 路径中所携带的类型是tree   
          const {data:res}=await this.$http.get('rights/tree')
          if(res.meta.status!==200){
              return this.$message.error('获取权限数据失败')
          }
          this.rightslist=res.data
          
      },

element的步骤条修改样式_html5_110

  • 在关闭分配权限对话框后需要对defKeys进行重置
<!-- 分配权限 -->
        <el-dialog
            title="分配权限"
            :visible.sync="setRightDialogVisible"
            width="50%"
            @close="setRightDialogCloesd"
//监听分配权限对话框的关闭事件
      setRightDialogCloesd(){
          this.defKeys=[]
      }
  • 调用api完成分配权限
<el-button type="primary" @click="allotRights">确 定</el-button>

指定ref元素

<!-- 树形控件 -->
            <el-tree :data="rightslist"
             :props="treeProps" 
             show-checkbox node-key="id" 
             default-expand-all
             :default-checked-keys="defKeys"
             ref="treeRef"></el-tree>

根据api 调用getCheckedKeys(),getHalfCheckedKeys()获取已勾选的节点和半勾选节点 使用…将其展开放到数组中

//点击为角色分配权限
        async allotRights(){
          const keys=[
              ...this.$refs.treeRef.getCheckedKeys(),
              ...this.$refs.treeRef.getHalfCheckedKeys()
          ]

          const idStr=keys.join(',')

          const {data:res}=await this.$http.post(`roles/${this.roleId}/rights`,{rids:idStr})

          if(res.meta.status!==200){
              return this.$message.error('分配权限失败')
          }
          this.$message.success('分配权限成功')
          this.getRolesList()
          this.setRightDialogVisible=false
      }

element的步骤条修改样式_vue.js_111

  • 完成分配角色功能
    Users.vue
    给分配角色按钮绑定事件
<!-- 分配角色 -->
                       <el-tooltip class="item" effect="dark" content="分配角色" placement="top">
                           <!-- 鼠标放置有文字提示 enterable使鼠标离开后隐藏-->
                          <el-button type="warning" :enterable="false" size="mini" icon="el-icon-setting"
                          @click="setRoles(scope.row)"></el-button>
                       </el-tooltip>    
                       <!--这里scope.row的数据源是userList对应的那一行数据-->

分配角色对话框

<!-- 分配角色 -->
        <el-dialog
        title="提示"
        :visible.sync="roleDialogVisible"
        width="50%"
       
        >
        <div>
            <p>当前的用户:{{userInfo.username}}</p>
            <p>当前的角色:{{userInfo.role_name}}</p>
          
        </div>
        <span slot="footer" class="dialog-footer">
            <el-button @click="roleDialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveRoleInfo">确 定</el-button>
        </span>
        </el-dialog>

在data中定义需要分配角色的用户信息,以及所有角色列表

// 需要分配角色的用户信息
			roleDialogVisible:false,
            userInfo:{},
            roleList:[],

定义分配角色对话框的事件

async setRoles(role){
            this.userInfo=role
            const {data:res}=await this.$http.get('roles')
            if(res.meta.status!==200){
                return this.$message.error('获取角色列表失败')
            }
            this.roleList=res.data
            this.roleDialogVisible=true
        },

增加分配角色下拉列表框组件

<div>
            <p>当前的用户:{{userInfo.username}}</p>
            <p>当前的角色:{{userInfo.role_name}}</p>
            <p>分配新角色:
               <el-select v-model="selectRole" placeholder="请选择">
                <el-option
                v-for="item in roleList"
                :key="item.id"
                :label="item.roleName"
                :value="item.id">
                </el-option>
            </el-select>
            </p>
        </div>

在data中定义selectRole 表示当前所选中的哪一项

selectRole:''

点击确定按钮完成角色的分配

<el-button type="primary" @click="saveRoleInfo">确 定</el-button>
// 点击按钮分配角色
        async saveRoleInfo(){
            if(!this.selectRole){
                return this.$message.error('请选择要分配的角色')
            }
            const {data:res}=await this.$http.put(`users/${this.userInfo.id}/role`,
            {
                rid:this.selectRole
            })

            if(res.meta.status!==200){
                return this.$message.error('更新角色失败')
            }

            this.$message.success('更新角色成功')
            this.getUserList()
            this.roleDialogVisible=false
        },

关闭对话框时重置

<!-- 分配角色 -->
        <el-dialog
        title="提示"
        :visible.sync="roleDialogVisible"
        width="50%"
        @close="setRoleDialog"
        >
// 关闭对话框重置
        setRoleDialog(){
            this.selectRole=''
            this.roleList=[]
        }

element的步骤条修改样式_ico_112


将完成的权限的相关功能上传到github

商品分类

  • 创建新的分支 并上传到github

element的步骤条修改样式_html5_113

新建goods目录 在其中新建Cates.vue 并创建对应的路由关系

children:[
        {path:'/welcome',component:welcome},
        {path:'/users',component:users},
        {path:'/rights',component:rights},
        {path:'/roles',component:roles},
        {path:'/categories',component:cates}
      ] }

初步绘制商品分类基本布局

<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-button type="primary">商品分类</el-button>
            </el-row>
        </el-card>
    </div>
</template>
  • 获取商品分类列表数据

Cates.vue

<script>
export default {
    data(){
        return{
            //查询条件
            queryInfo:{
                type:3,
                pagenum:1,
                pagesize:5
            },
            // 商品分类的数据列表 默认为空
            catelist:[],
            // 总数据条数
            total:0

        }
    },
    created(){
        this.getCateList()
    },
    methods:{
        // 获取商品分类列表数据
        
        async getCateList(){
            const {data:res}=await this.$http.get('categories',{
                params:this.queryInfo
            })
            if(res.meta.status!==200){
                return this.$message.error('获取分类数据失败')
            }
            this.catelist=res.data.result
            this.total=res.data.total
        }
    }
}
</script>

//注意1:传递的对象名称必须为params!!! 否则不会根据分页情况来显示数据 因为当对象名不为指定的params 会默认传递的type,pagesize,pagenum为空 从而返回所有的数据
//注意2 this.catelist要赋值为res.data.result 而不是res.data

element的步骤条修改样式_element的步骤条修改样式_114

  • 使用vue-table-with-tree-grid第三方插件渲染下拉表格插件

在vue ui 安装vue-table-with-tree-grid运行环境依赖(不知道为啥安装不上 后来使用npm i vue-table-with-tree-grid --save)

根据该插件的github提供的api文档 和 example来具体使用该插件

注册插件

import TreeTable from 'vue-table-with-tree-grid'
Vue.component('tree-table',TreeTable)

Cates.vue

<!-- 分类表格 -->
            <tree-table :data="catelist" :columns="columns"></tree-table>

在data中定义

columns:[{
                label:'分类名称',
                prop:'cat_name'
            }]

element的步骤条修改样式_element的步骤条修改样式_115


element的步骤条修改样式_html5_116


设置表格的属性

<tree-table :data="catelist" :columns="columns" 
            :selection-type="false" :expand-type="false"
            :show-index="true" index-text="#" border>

selection-type 是否为多选类型表格

expand-type 是否为展开行类型表格

show-index 是否展示索引列

index-text 索引列标题

element的步骤条修改样式_element的步骤条修改样式_117

  • 使用自定义模板列渲染表格数据

根据提供的example可知

element的步骤条修改样式_javascript_118


type:template 指定这一列要渲染成自定义模板列

template:likes 指定该自定义模板列使用的作用域插槽名

在data中定义

columns:[{
                label:'分类名称',
                prop:'cat_name'
            },{
                label:'是否有效',
                // 表示将当前定义为模板列
                type:'template',
                // 表示当前这一列使用的模板名称
                template:'isok'
            }]

使用slot给自定义模板列命名 并且v-if按需显示对应的图标

<!-- 分类表格 -->
            <tree-table :data="catelist" :columns="columns" 
            :selection-type="false" :expand-type="false"
            :show-index="true" index-text="#" border>
                <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:lightgreen"></i>
                </template>
            </tree-table>

element的步骤条修改样式_javascript_119

  • 渲染排序列

在columns中定义

{
                label:'排序',
                type:'template',
                template:'order'
            }

按需显示对应的标签等级

<!-- 排序 -->
                <template slot="order" slot-scope="scope">
                    <el-tag size="mini" v-if="scope.row.cat_level===0">一级</el-tag>
                    <el-tag size="mini" v-else-if="scope.row.cat_level===1" type="success">二级</el-tag>
                    <el-tag size="mini" v-else type="warning">三级</el-tag>
                </template>
  • 渲染操作列
{
                label:'操作',
                type:'template',
                template:'opt'
            }
<!-- 操作 -->
                <template slot="opt" >
                    <el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button>
                    <el-button type="danger" icon="el-icon-delete" size="mini">删除</el-button>
                </template>

element的步骤条修改样式_element的步骤条修改样式_120

  • 实现分页功能
<!-- 分页 -->
            <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="queryInfo.pagenum"
            :page-sizes="[4,6,8,10]"
            :page-size="queryInfo.pagesize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="total">
           </el-pagination>
// 监听一页显示数据的多少
        handleSizeChange(newSize){
            this.queryInfo.pagesize=newSize
            this.getCateList()
        },
        // 监听页码的多少
        handleCurrentChange(newpage){
            this.pagenum=newpage
        },

element的步骤条修改样式_element的步骤条修改样式_121

  • 添加分类
<!-- 添加分类的对话框 -->
        <el-dialog
        title="添加分类"
        :visible.sync="addCateDialogVisible"
        width="40%"
      
        >
        <el-form :model="addCateForm" :rules="addCateRules" ref="addCateFormRef" label-width="100px"  class="demo-ruleForm">
            <el-form-item label="分类名称" prop="cat_name">
                <el-input v-model="addCateForm.cat_name"></el-input>
            </el-form-item>
           
        </el-form>    
        <span slot="footer" class="dialog-footer">
            <el-button @click="addCateDialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="addCateDialogVisible = false">确 定</el-button>
        </span>
        </el-dialog>
addCateDialogVisible:false,
            addCateForm:{
                cat_name:'',
            
            },
            addCateRules: {
                cat_name: [
                    { required: true, message: '请输入分类名称', trigger: 'blur' },
                    {  max: 10, message: '长度在10个字符以内', trigger: 'blur' }
                ],
            },

注意:表单校验规则的名称要和v-model双向数据绑定的名称一致 都要为cat_name

<el-button type="primary" @click="showAddCateDialog">添加分类</el-button>
// 点击按钮展示添加分类的对话框
        showAddCateDialog(){
            this.addCateDialogVisible=true
        },
        resetaddCateForm(){
            this.$refs.addCateFormRef.resetFields()
        }
  • 渲染级联选择框控件

在addCateForm新增两个属性

addCateForm:{
                cat_name:'',
                 // 父级分类的id
                cat_pid:0,
                // 默认要添加分类的等级是1级分类
                cat_level:0 
            },

定义父级分类列表数组

parenCateList:[]

定义获取父级分类的数据列表方法
传递的对象参数名必须为params

async getParentCateList(){
            // type:2 获取前两级的所有分类
            const {data:res}=await this.$http.get('categories',{params:{type:2}})
            if(res.meta.status!==200){
                return this.$message.error('获取父级分类失败')
            }
            this.parenCateList=res.data
        }

调用

// 点击按钮展示添加分类的对话框
        showAddCateDialog(){
            // 获取父级分类的数据列表
            this.getParentCateList()
            this.addCateDialogVisible=true

        },

一共获取到了30个父级分类

element的步骤条修改样式_element的步骤条修改样式_122

使用级联控件 并修改设置属性

<el-form-item label="分类名称" prop="name">
                <el-input v-model="addCateForm.cat_name"></el-input>
            </el-form-item>
            <el-form-item label="父级分类">
                <!-- option 用来指定的数据源 
                     props 指定相应的配置对象
                     clearable 是否可以清空
                     change-on-select 是否可以选中任意一级
                     (默认只能选中最后一级)-->
               <el-cascader      
                :options="parenCateList"
                @change="parentCateChange"
                :props="cascaderProps"
                v-model="selectedKeys"
                class="cas-width"
                clearable
                change-on-select
                >
               </el-cascader>

其中 父级分类不需要进行表单验证 因为当什么都没有选择时默认将添加的分类 作为父级分类添加(最高一级)

props属性说明
value:指定选项的值为选项对象的某个属性值
label 指定选项标签为选项对象的某个属性值
children:指定选项的子选项为选项对象的某个属性值

// 父级分类的列表
            parenCateList:[],
            // 指定级联选择器的数据对象
            cascaderProps:{
                expandTrigger: 'hover' ,
                value:'cat_id',
                label:'cat_name',
                children:'children'
            },
            // 选中的父级分类id数组
            selectedKeys:[]

定义parentCateChange 当选项发生改变时立即触发

// 选择项改变立即触发这个函数
        parentCateChange(){
			 console.log(this.selectedKeys)
        }

element的步骤条修改样式_javascript_123


element的步骤条修改样式_ico_124


element的步骤条修改样式_html5_125


element的步骤条修改样式_javascript_126


由于 默认的级联控件过高 导致一部分数据被顶部覆盖 所以可以在全局中设置对应样式

global.css

.el-cascader-panel {
    height: 200px !important
}

使该控件的宽度为100%

.cas-width{
    width: 100%;
}

element的步骤条修改样式_ico_127


element的步骤条修改样式_element的步骤条修改样式_128


element的步骤条修改样式_element的步骤条修改样式_129


element的步骤条修改样式_ico_130

  • 根据父分类的变换来处理表单中的数据
// 选择项改变立即触发这个函数
        parentCateChange(){
           
            //如果seletedKeys数组中的length大于0 证明选中的父级分类
            //繁殖 就说明没有选中任何父级分类
            if(this.selectedKeys.length>0){
                //父级分类的id
                this.addCateForm.cat_pid=this.selectedKeys[this.selectedKeys.length-1]
                  //为当前分类的等级赋值
                this.addCateForm.cat_level=this.selectedKeys.length
                return
            }else{
                //父级分类的id
                this.addCateForm.cat_pid=0
                  //为当前分类的等级赋值
                this.addCateForm.cat_level=0
            }
        }
<el-button type="primary" @click="addCate">确 定</el-button>
addCate(){
            console.log(this.addCateForm)
        }

此时点击确定按钮 addCateForm中的数据会随着父级分类选项框的变化而变化

element的步骤条修改样式_ico_131


element的步骤条修改样式_vue.js_132


element的步骤条修改样式_vue.js_133


element的步骤条修改样式_ico_134


element的步骤条修改样式_javascript_135


element的步骤条修改样式_ico_136


点击关闭和取消重置表单

<!-- 添加分类的对话框 -->
        <el-dialog
        title="添加分类"
        :visible.sync="addCateDialogVisible"
        width="40%"
        @close="resetaddCateForm"

        >
<el-button @click="resetaddCateForm">取 消</el-button>
resetaddCateForm(){
            this.$refs.addCateFormRef.resetFields()
            this.selectedKeys=[]
            this.addCateForm.cat_level=0
            this.addCateForm.cat_pid=0
        }
  • 完成商品分类
    注意:此处post 不能写为params:{this.addCateForm} (之前就必须这么写,不知道为啥。。。)
addCate(){
            // console.log(this.addCateForm)
             this.$refs.addCateFormRef.validate(async valid=>{
                 if(!valid) return
                 const {data:res}=await this.$http.post('categories',this.addCateForm)
                 if(res.meta.status!==201){
                     return this.$message.error('添加分类失败')
                 }
                 this.$message.success('添加分类成功')
                 this.getCateList()
                 this.addCateDialogVisible=false
             })
        },

element的步骤条修改样式_vue.js_137


element的步骤条修改样式_element的步骤条修改样式_138


element的步骤条修改样式_element的步骤条修改样式_139


element的步骤条修改样式_vue.js_140


element的步骤条修改样式_javascript_141


element的步骤条修改样式_vue.js_142


element的步骤条修改样式_vue.js_143

element的步骤条修改样式_html5_144

  • 上传代码
    将代码提交到本地仓储

element的步骤条修改样式_vue.js_145


element的步骤条修改样式_vue.js_146


将本地goods_cate分支提交到远程

element的步骤条修改样式_html5_147


切换到主分支合并goods_cate分支

element的步骤条修改样式_element的步骤条修改样式_148


将主分支提交到github

element的步骤条修改样式_javascript_149

分类参数

  • 创建新的分支上传到github

element的步骤条修改样式_html5_150

  • 参数管理功能介绍

element的步骤条修改样式_javascript_151


新建组件params.vue 并创建对应的路由关系

  • 初步绘制ui

params.vue

<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="cat_opt">
                <el-col>
                    <span>选择商品分类</span>

                </el-col>
            </el-row>
        </el-card>

    </div>
</template>
.cat_opt{
    margin: 15px 0;
}

element的步骤条修改样式_javascript_152

  • 实现级联选择功能

获取所有商品分类数据

data(){
        return{
            cateList:[],
created(){
        this.getCateList()
    },
    methods:{
        // 获取所有商品分类
        async  getCateList(){
            const {data:res}=await this.$http.get('categories')
            if(res.meta.status!==200){
                return this.$message.error('获取商品分类列表失败')
            }
            this.cateList=res.data
            console.log(this.cateList)
        },
<!-- 选择商品分类区域 -->
            <el-row class="cat_opt">
                <el-col>
                    <span class="sel_span">选择商品分类:</span>
                    <el-cascader
                    v-model="sel_key"
                    :options="cateList"
                    :props="props"
                    @change="handleChange"></el-cascader>
                </el-col>
            </el-row>
// 级联选择框双向绑定的数组
            sel_key:[],
            props:{
                expandTrigger: 'hover',
                value:'cat_id',
                label:'cat_name',
                children:'children'
            }
// 选中分类发生变化
        handleChange(val){
           
            // 证明选中的不是三级分类
            if(this.sel_key.length!==3){
                this.sel_key=[]
                return
            }

            // 证明选中的是三级分类
            
        }

二级菜单选中无效

element的步骤条修改样式_ico_153


element的步骤条修改样式_ico_154

  • 渲染动态参数和静态属性的标签页
<!-- tab 页签区域 -->
            <el-tabs v-model="activeName" @tab-click="handleClick">
                <el-tab-pane label="动态参数" name="first">动态参数</el-tab-pane>
                <el-tab-pane label="静态属性" name="second">静态属性</el-tab-pane>
               
            </el-tabs>

默认展示first 动态参数

// 被激活的页签名称
            activeName:'first'
handleClick(){
            
        }

element的步骤条修改样式_javascript_155

  • 渲染添加参数和添加属性按钮 并控制其选中状态

如果级联选择器选择的不是三级分类 或者未做选择按钮都为禁用状态

computed:{
        isBtnDisabled(){
            if(this.sel_key.length!==3){
                return true
            }
            return false
        }
    }
<el-tab-pane label="动态参数" name="first">
                    <el-button type="primary" :disabled="isBtnDisabled">添加参数</el-button>
                </el-tab-pane>
                <el-tab-pane label="静态属性" name="second">
                    <el-button type="primary" :disabled="isBtnDisabled">添加属性</el-button>
                </el-tab-pane>

element的步骤条修改样式_html5_156


element的步骤条修改样式_ico_157


element的步骤条修改样式_javascript_158

  • 获取参数列表数据

根据api 文档 要传递分类id和参数sel sel值不能为空,通过 only 或 many 来获取分类静态参数还是动态参数

将激活的页面名称分别改为many,only

<el-tab-pane label="动态参数" name="many">
<el-tab-pane label="静态属性" name="only">

在computed中定义cateId()方法 计算当前选中的三级分类的id

// 当前选中的三级分类的id
        cateId(){
            if(this.sel_key.length===3){
                return this.sel_key[this.sel_key.length-1]
            }
            return null
        }

当选中的分类发生变化时 事实获取对应的分类参数

// 选中分类发生变化
        async handleChange(val){
           
            // 证明选中的不是三级分类
            if(this.sel_key.length!==3){
                this.sel_key=[]
                return
            }
           
            // 证明选中的是三级分类
            // 根据所选分类的id 和当前所处的面板 获取对应的参数
            const{data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,
                {params:{sel:this.activeName}
            })
            if(res.meta.status!==200){
               
                return this.$message.error('获取参数列表失败')
            }
            console.log(res.data)

        },

element的步骤条修改样式_element的步骤条修改样式_159


element的步骤条修改样式_html5_160

  • 切换tabs面板后重新获取参数列表数据

由于之前handleChange里的获取分类数据只属于级联选择器 当切换面板时 并不会重新获取数据 所以应该将handleChange里所有的代码抽离出一个单独的方法 之后在handleChange 和 handleClick 中都分别调用这两个方法

// 获取参数的列表数据
        async getParamsData(){
             // 证明选中的不是三级分类
            if(this.sel_key.length!==3){
                this.sel_key=[]
                return
            }
            // 证明选中的是三级分类
            // 根据所选分类的id 和当前所处的面板 获取对应的参数
            const{data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,
                {params:{sel:this.activeName}
            })
            if(res.meta.status!==200){
               
                return this.$message.error('获取参数列表失败')
            }
            console.log(res.data)
        },
// 选中分类发生变化
        handleChange(){
           this.getParamsData()
        },
      
        // 标签页面板发生变化
        handleClick(){
            this.getParamsData()
        }

element的步骤条修改样式_element的步骤条修改样式_161


element的步骤条修改样式_html5_162


element的步骤条修改样式_element的步骤条修改样式_163


element的步骤条修改样式_html5_164

  • 将获取到的参数挂载到不同的数据源上

由于不同的标签页面板所使用的返回的数据不同 我们需要绑定到不同的数据源上

// 动态参数的数据
            manyTableData:[],
            // 静态属性的数据
            onlyTableData:[]
// 获取参数的列表数据
        async getParamsData(){
             // 证明选中的不是三级分类
            if(this.sel_key.length!==3){
                this.sel_key=[]
                return
            }
            // 证明选中的是三级分类
            // 根据所选分类的id 和当前所处的面板 获取对应的参数
            const{data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,
                {params:{sel:this.activeName}
            })
            if(res.meta.status!==200){
               
                return this.$message.error('获取参数列表失败')
            }
            console.log(res.data)
            // 绑定不同的数据源
            if(this.activeName==='many'){
                this.manyTableData=res.data
            }else{
                this.onlyTableData=res.data
            }
        },
  • 渲染动态参数和静态属性表格
<!-- tab 页签区域 -->
            <el-tabs v-model="activeName" @tab-click="handleClick">
                <el-tab-pane label="动态参数" name="many">
                    <el-button type="primary" :disabled="isBtnDisabled">添加参数</el-button>

                    <!-- 动态参数表格 -->
                    <el-table :data="manyTableData" border stripe>
                        <!-- 展开行 -->
                         <el-table-column type="expand"></el-table-column>
                         <!-- 索引列 -->
                        <el-table-column type="index"></el-table-column>
                        <el-table-column label="参数名称" prop="attr_name"></el-table-column>
                        <el-table-column label="操作">
                             <template >
                                 <el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button>
                                 <el-button type="danger" icon="el-icon-delete"  size="mini">删除</el-button>
                             </template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>

                <el-tab-pane label="静态属性" name="only">
                    <el-button type="primary" :disabled="isBtnDisabled">添加属性</el-button>
                    
                    <!-- 静态属性表格 -->
                    <el-table :data="onlyTableData" border stripe>
                        <!-- 展开行 -->
                         <el-table-column type="expand"></el-table-column>
                         <!-- 索引列 -->
                        <el-table-column type="index"></el-table-column>
                        <el-table-column label="属性名称" prop="attr_name"></el-table-column>
                        <el-table-column label="操作">
                             <template >
                                 <el-button type="primary" icon="el-icon-edit" size="mini">编辑</el-button>
                                 <el-button type="danger" icon="el-icon-delete"  size="mini">删除</el-button>
                             </template>
                        </el-table-column>
                    </el-table>
                </el-tab-pane>
               
            </el-tabs>

element的步骤条修改样式_javascript_165


element的步骤条修改样式_javascript_166

  • 实现添加参数对话框

此时两个标签页的按钮可以使用同一个对话框 此时对话框的title等一些属性要根据点击不同标签页的不同按钮动态绑定
在computed中定义titleText() 动态计算标题的面板

// 动态计算标题的面板
        titleText(){
            if(this.activeName==='many'){
                return '动态参数'
            }
            return '静态属性'
        }
<!-- 添加参数的对话框 -->
        <el-dialog
        :title="'添加'+titleText"
        :visible.sync="addDialogVisible"
        width="50%"
        @close="addDialogClose"
        >
        <!-- 添加参数的表单 -->
        <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="addDialogVisible = false">确 定</el-button>
        </span>
        </el-dialog>
// 控制添加对话框的显示与隐藏
            addDialogVisible:false,
            // 添加参数的表单数据
            addForm:{
                 attr_name:''
            },
            addFormRules:{
               attr_name: [
                    { required: true, message: '请输入参数名称', trigger: 'blur' },
                    { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
                ]
            }
// 监听添加对话框的关闭事件
        addDialogClose(){
            this.$refs.addFormRef.resetFields();
        }

element的步骤条修改样式_element的步骤条修改样式_167


element的步骤条修改样式_html5_168

  • 完成动态参数和静态属性的操作
<el-button type="primary" @click="addParams">确 定</el-button>

根据api 其中传递的id参数为分类id 可以根据computed的cateId来获取

addParams(){
            this.$refs.addFormRef.validate(async valid=>{
                if(!valid) return
                const {data:res}=await this.$http.post(`categories/${this.cateId}/attributes`,{
                    attr_name:this.addForm.attr_name,
                    attr_sel:this.activeName
                })

                if(res.meta.status!==201){
                    return this.$message.error('添加参数失败')
                }

                this.$message.success('添加参数成功')
                this.addDialogVisible=false
                
                this.getParamsData()
            })
        }

element的步骤条修改样式_ico_169


element的步骤条修改样式_javascript_170


element的步骤条修改样式_element的步骤条修改样式_171


element的步骤条修改样式_javascript_172

  • 渲染编辑对话框

分别给动态参数和静态属性的编辑按钮绑定showEditDialog事件

<el-button type="primary" icon="el-icon-edit" size="mini" @click="showEditDialog">编辑</el-button>
<!-- 修改参数的对话框 -->
        <el-dialog
        :title="'修改'+titleText"
        :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="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>
// 修改参数对话框
            editDialogVisible:false,
            editForm:{
                attr_name:''
            },
            editFormRules:{
                attr_name: [
                    { required: true, message: '请输入参数名称', trigger: 'blur' },
                    { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
                ]
            }
// 修改参数对话框
        showEditDialog(){
            this.editDialogVisible=true
        },
        editDialogClose(){
             this.$refs.editFormRef.resetFields();
        },
        editParams(){

        }

element的步骤条修改样式_ico_173


element的步骤条修改样式_ico_174

  • 提交编辑参数
<el-button type="primary" @click="editParams">确 定</el-button>
// 提交编辑参数
        editParams(){
            this.$refs.editFormRef.validate(async valid => {
               console.log(this.editForm)
                if (valid) {
                   const {data:res}=await this.$http.put(`categories/${this.cateId}/attributes/${this.editForm.attr_id}`,{
                       
                           attr_name:this.editForm.attr_name,
                           attr_sel:this.activeName
                       
                   })
                   if(res.meta.status!==200){
                    //    console.log(res.meta)
                       return this.$message.error('编辑参数失败')
                   }
                   this.getParamsData()
                   this.editDialogVisible=false
                } else {
                    console.log('error submit!!');
                    return false;
                }
            });
        },

element的步骤条修改样式_ico_175

element的步骤条修改样式_element的步骤条修改样式_176


element的步骤条修改样式_html5_177


element的步骤条修改样式_vue.js_178


element的步骤条修改样式_ico_179


element的步骤条修改样式_html5_180

  • 删除参数
<el-button type="danger" icon="el-icon-delete"  size="mini" @click="delAttr(scope.row.attr_id)">删除</el-button>
<el-button type="danger" icon="el-icon-delete"  size="mini" @click="delAttr(scope.row.attr_id)">删除</el-button>
// 删除参数
        async delAttr(attrId){
            const msg=await this.$confirm('此操作将永久删除该参数, 是否继续?', '提示', {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning'
            }).catch(err=>err)
            console.log(msg)
            if(msg=='cancel'){
                return this.$message.info("用户取消了操作")
            }
            const {data:res}=await this.$http.delete(`categories/${this.cateId}/attributes/${attrId}`)
            if(res.meta.status!==200){
                return this.$message.error('删除参数失败')
            }
            this.getParamsData()
        }

element的步骤条修改样式_javascript_181

  • 渲染参数下的可选项

由于参数列表中attr_vals是一个以空格分割的字符串 所以要将其变为一个数组

// 获取参数的列表数据
        async getParamsData(){
             // 证明选中的不是三级分类
            if(this.sel_key.length!==3){
                this.sel_key=[]
                return
            }
            // 证明选中的是三级分类
            // 根据所选分类的id 和当前所处的面板 获取对应的参数
            const{data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,
                {params:{sel:this.activeName}
            })
            if(res.meta.status!==200){
               
                return this.$message.error('获取参数列表失败')
            }
            
            // 将字符串变为数组
            res.data.forEach((item)=>{
               
                item.attr_vals=item.attr_vals.split(' ')
               
            })
            if(this.activeName==='many'){
                this.manyTableData=res.data
            }else{
                this.onlyTableData=res.data
            }
        },

在展开行中用tag渲染

<!-- 动态参数表格 -->
                    <el-table :data="manyTableData" border stripe>
                        <!-- 展开行 -->
                         <el-table-column type="expand">
                             <template slot-scope="scope">
                                 <el-tag closable v-for="(item,i) in scope.row.attr_vals" :key="i">
                                     {{item}}
                                 </el-tag>
                             </template>
                         </el-table-column>

element的步骤条修改样式_element的步骤条修改样式_182

  • 解决attr_vals为空时出现的bug

当attr_vals为空时 此时按照空格经行分割会得到空字符串

element的步骤条修改样式_vue.js_183


应该对分割前进行判断

注意:在三元判断中当attr_vals为空时应该等于一个空数组而不是’ '(空)值

res.data.forEach((item)=>{
               
                item.attr_vals=item.attr_vals===''?[]:item.attr_vals.split(' ')
               
            })
  • 控制文本框与按钮的切换显示

使用新建标签的组件

<!-- new tag -->
 <!--@keyup.enter.native:按回车
     @blur:失去焦点-->
                                 <!-- 输入文本框 -->
                                 <el-input
                                class="input-new-tag"
                                v-if="inputVisible"
                                v-model="inputValue"
                                ref="saveTagInput"
                                size="small"
                                @keyup.enter.native="handleInputConfirm"
                                @blur="handleInputConfirm"
                                >
                                </el-input>
                                <!-- 添加按钮 -->
                                <el-button v-else class="button-new-tag" size="small" @click="showInput">+ New Tag</el-button>
// 控制按钮与文本框的切换显示
            inputVisible: false,
            // 文本框中输入的内容
            inputValue: ''
// 文本框失去了焦点或者按enter键都会触发
        handleInputConfirm() {
           
        },
        // 显示文本输入框
        showInput() {
          this.inputVisible = true;
          
        },

修改样式

.input-new-tag {
    width: 90px;
  }

element的步骤条修改样式_ico_184


element的步骤条修改样式_ico_185

  • 为每一行的数据单独提供inputVisible和inputValue

由于共同绑定了这一个数据源 所以当编辑一个文本框的时候 会影响到其他行

element的步骤条修改样式_html5_186


我们需要为每一行数据单独提供这两个值

循环为每一项增加这两个属性值

async getParamsData(){
             // 证明选中的不是三级分类
            if(this.sel_key.length!==3){
                this.sel_key=[]
                return
            }
            // 证明选中的是三级分类
            // 根据所选分类的id 和当前所处的面板 获取对应的参数
            const{data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,
                {params:{sel:this.activeName}
            })
            if(res.meta.status!==200){
               
                return this.$message.error('获取参数列表失败')
            }
            
            // 将字符串变为数组
            res.data.forEach((item)=>{
                item.attr_vals=item.attr_vals===''?'':item.attr_vals.split(' ')
                // 控制文本框的显示与隐藏
                item.inputVisible=false
                // 文本框中输入的值
                item.inputValue=""
            })
            if(this.activeName==='many'){
                this.manyTableData=res.data
            }else{
                this.onlyTableData=res.data
            }
        }

修改组件之前绑定的数据源

<el-input
                                class="input-new-tag"
                                v-if="scope.row.inputVisible"
                                v-model="scope.row.inputValue"
                                ref="saveTagInput"
                                size="small"
                                @keyup.enter.native="handleInputConfirm"
                                @blur="handleInputConfirm"
                                >
                                </el-input>
                                <!-- 添加按钮 -->
                                <el-button v-else class="button-new-tag" size="small" 
                                @click="showInput(scope.row)">+ New Tag</el-button>
// 显示文本输入框
        showInput(row) {
         row.inputVisible=true
          
        },
  • 让文本框自动获得焦点
showInput(row) {
            row.inputVisible=true
            //  让文本框自动获得焦点
            // $nextTick方法的作用 就是当row.inputVisible重置为true
            //  后 页面并没有重新渲染 此时并没有input这个元素 所以要等
            //   将页面重新渲染之后 才会指定回调函数中的代码
            this.$nextTick(_ => {
                this.$refs.saveTagInput.$refs.input.focus();
            });

失去焦点或者按enter

<!-- 输入文本框 -->
                                 <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>
// 文本框失去了焦点或者按enter键都会触发
        handleInputConfirm(row) {
            // 若输入的全是空格 失去焦点清空重置
           if(row.inputValue.trim().length===0){
               row.inputValue=''
               row.inputVisible=false
               return
           }
        //    如果没有return 则证明输入的内容 需要做后续处理
        }
  • 完成参数可选项的添加操作
// 文本框失去了焦点或者按enter键都会触发
        async handleInputConfirm(row) {
            // 若输入的全是空格 失去焦点清空重置
           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
            // 需要发起请求 保存
            const {data:res}=await this.$http.put(`categories/${this.cateId}/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('修改参数失败')
            }
            this.$message.success('修改参数成功')
        },

element的步骤条修改样式_javascript_187

  • 删除对应参数可选项
<el-tag closable @close="handleClose(i,scope.row)" v-for="(item,i) in scope.row.attr_vals" :key="i">
                                     {{item}}
                                 </el-tag>

将原先handleInputConfirm方法内的部分代码封装

// 将对attr_vals的操作 保存到数据库
        async saveAttrVals(row){
            
            
            // 需要发起请求 保存
            const {data:res}=await this.$http.put(`categories/${this.cateId}/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('修改参数失败')
            }
            this.$message.success('修改参数成功')
        },

在handleInputConfirm中调用

// 文本框失去了焦点或者按enter键都会触发
        async handleInputConfirm(row) {
            // 若输入的全是空格 失去焦点清空重置
           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)
        },

同时定义handleClose 在其中调用

// 删除对应参数的可选项
        handleClose(i,row){
            row.attr_vals.splice(i,1)
            
            this.saveAttrVals(row)
        }

element的步骤条修改样式_html5_188


element的步骤条修改样式_html5_189

  • 当选中二级分类时清空表格数据
// 获取参数的列表数据
        async getParamsData(){
             // 证明选中的不是三级分类
            if(this.sel_key.length!==3){
                this.sel_key=[]
                this.manyTableData=[]
                this.onlyTableData=[]
                return
            }

element的步骤条修改样式_html5_190


element的步骤条修改样式_vue.js_191

商品列表

创建goods_list分支 并上传到github
创建list.vue组件 并建立对应路由关系

初始化界面

<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 :gutter="20">
                <el-col :span="8" >
                    <el-input placeholder="请输入密码">
                        <el-button slot="append" icon="el-icon-search"></el-button>
                    </el-input>
                </el-col>
                <el-col :span="4">
                    <el-button type="primary">添加商品</el-button>
                </el-col>
            </el-row>
        </el-card>
    </div>
</template>
<script>
export default {
    data(){
        return{

        }
    },
    created(){

    },
    methods:{

    }
}
</script>

<style lang="less" scoped>

</style>

element的步骤条修改样式_javascript_192

获取商品列表数据

data(){
        return{
            pagenum:1,
            pagesize:5,
            query:'',
            goodslist:[],
            total:0
        }
    },
async getGoodsList(){
            const {data:res}=await this.$http.get('goods',{
               params:{
                   query:this.query,
                   pagenum:this.pagenum,
                   pagesize:this.pagesize
               }
            })
            if(res.meta.status!==200){
                console.log(res.meta)
                return this.$message.error('获取列表失败')
            }
            this.goodslist=res.data.goods
            this.total=res.data.total
            this.$message.success('获取列表成功')
            // console.log(this.goodslist)

        },
created(){
        this.getGoodsList()
    },

渲染商品列表

<el-table
            :data="goodslist"
            border
            style="width: 100%"
            stripe>
               <el-table-column type="index" label="#">
               </el-table-column>
                <el-table-column
                prop="goods_name"
                label="商品名称"
                width="580"
                >
                </el-table-column>
                <el-table-column
                prop="goods_price"
                label="商品价格(元)"
                width="100"
                >
                </el-table-column>
                <el-table-column
                prop="goods_weight"
                label="商品重量"
                width="100"
                >
                </el-table-column>
                <el-table-column  label="创建时间">
                    <template slot-scope="scope">
                        <span>{{scope.row.add_time}}</span>
                    </template>

                </el-table-column>
                <el-table-column label="操作">
                    <template slot-scope="scope">
                        <el-button type="primary" size="mini"  icon="el-icon-edit" ></el-button>
                        <el-button type="danger" size="mini"  icon="el-icon-delete" @click="deleteGoods(scope.row.goods_id)" ></el-button>
                    </template>
                </el-table-column>
            </el-table>
  • 添加全局过滤器格式时间

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')

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
})
<template slot-scope="scope">
                        <span>{{scope.row.add_time|dataFormat}}</span>
                    </template>

实现分页功能

<el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="this.pagenum"
            :page-sizes="[5, 8, 9, 12]"
            :page-size="this.pagesize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="this.total">
            </el-pagination>
handleSizeChange(newSize){
            this.pagesize=newSize
             this.getGoodsList()
        },
        handleCurrentChange(newPage){
            this.pagenum=newPage
            this.getGoodsList()
        },
  • 实现搜素和清空功能

给v-model绑定query数据源 绑定click点击事件调用获取商品列表方法

<el-input placeholder="请输入内容"  v-model="query">
                        <el-button slot="append" icon="el-icon-search" @click="getGoodsList"></el-button>
                    </el-input>

添加clearable属性 绑定clear事件 清空后默认获取全部的数据

<el-input placeholder="请输入内容"  v-model="query" clearable @clear="getGoodsList">
                        <el-button slot="append" icon="el-icon-search" @click="getGoodsList"></el-button>

element的步骤条修改样式_element的步骤条修改样式_193


element的步骤条修改样式_ico_194

实现删除功能

<template slot-scope="scope">
                        <el-button type="primary" size="mini"  icon="el-icon-edit" ></el-button>
                        <el-button type="danger" size="mini"  icon="el-icon-delete" @click="deleteGoods(scope.row.goods_id)" ></el-button>
                    </template>
async deleteGoods(id){
            const confirm=await this.$confirm('此操作将永久删除该商品, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
            }).catch(err=>err)
            // console.log(confirm)
            if(confirm=='cancel'){
                return this.$message.info('用户取消了操作')
            }
            else{
                const {data:res}=await this.$http.delete(`goods/${id}`)
                if(res.meta.status!==200){
                    return this.$message.error('删除商品失败')
                }
                this.getGoodsList()
            }
            
        }

element的步骤条修改样式_vue.js_195

添加商品

  • 点击添加商品通过编程式导航跳转到添加商品页面

list.vue

<el-col :span="4">
                    <el-button type="primary" @click="goAddpage">添加商品</el-button>
                </el-col>
goAddpage(){
            this.$router.push('/goods/add')
        }

创建Add.vue组件

建立路由对应关系

import add from '../components/goods/Add.vue'
{path:'/goods/add',component:add}

element的步骤条修改样式_html5_196

  • 添加提示信息组件和步骤条组件
<el-card>
             <el-alert
                title="添加商品信息"
                type="info"
                show-icon>
            </el-alert>
            <el-steps :space="200" :active="activeIndex" finish-status="success" 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>
        </el-card>
return{
            activeIndex:0
        }

设置全局样式
.global.css

.el-steps{
    margin: 15px 0;
}
.el-step_title{
    font-size: 13px;
}

element的步骤条修改样式_vue.js_197

  • 渲染tab栏区域
<el-tabs :tab-position="'left'" style="height: 200px;">
                <el-tab-pane label="基本信息">基本信息</el-tab-pane>
                <el-tab-pane label="商品参数">商品参数</el-tab-pane>
                <el-tab-pane label="商品属性">商品属性</el-tab-pane>
                <el-tab-pane label="商品图片">商品图片</el-tab-pane>
                <el-tab-pane label="商品内容">商品内容</el-tab-pane>
            </el-tabs>

element的步骤条修改样式_ico_198

  • 实现步骤条和tab栏的数据联动

给tabs数据绑定activeIndex 并增加name属性 是其改变时将name的值同步到v-model

<el-tabs :tab-position="'left'" v-model="activeIndex" style="height: 200px;">
                <el-tab-pane label="基本信息" name="0">基本信息</el-tab-pane>
                <el-tab-pane label="商品参数" name="1">商品参数</el-tab-pane>
                <el-tab-pane label="商品属性" name="2">商品属性</el-tab-pane>
                <el-tab-pane label="商品图片" name="3">商品图片</el-tab-pane>
                <el-tab-pane label="商品内容" name="4">商品内容</el-tab-pane>
            </el-tabs>

此时steps的active也绑定的是activeIndex 但由于该组件的active属性必须是数字类型 所以要转化

<el-steps :space="200" :active="activeIndex-0" finish-status="success" align-center>

element的步骤条修改样式_html5_199


element的步骤条修改样式_ico_200

在tab外围添加表单组件

<el-form :model="addForm" :rules="addRules" ref="addRuleForm" label-width="100px" label-position="top">
                <el-tabs :tab-position="'left'" v-model="activeIndex" style="height: 200px;">
                    <el-tab-pane label="基本信息" name="0">基本信息</el-tab-pane>
                    <el-tab-pane label="商品参数" name="1">商品参数</el-tab-pane>
                    <el-tab-pane label="商品属性" name="2">商品属性</el-tab-pane>
                    <el-tab-pane label="商品图片" name="3">商品图片</el-tab-pane>
                    <el-tab-pane label="商品内容" name="4">商品内容</el-tab-pane>
                </el-tabs>
            </el-form>
addForm:{}
  • 绘制基本信息面板ui结构

将tab的固定高度200去除

<el-tab-pane label="基本信息" name="0">
                        <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_weight">
                            <el-input v-model="addForm.goods_weight" 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-tab-pane>
addForm:{
                goods_name:'',
                goods_price:0,
                goods_weight:0,
                goods_number:0
            },
            addRules:{
                goods_name:[
                    { required: true, message: '请输入商品名称', trigger: 'blur' },
                    { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
                ],   
                goods_price:[
                    { required: true, message: '请输入商品价格', trigger: 'blur' },
                    { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
                ],
                goods_weight:[
                    { required: true, message: '请输入商品重量', trigger: 'blur' },
                    { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
                ],
                goods_number:[
                    { required: true, message: '请输入商品数量', trigger: 'blur' },
                    { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
                ],  
             }

element的步骤条修改样式_vue.js_201

  • 绘制商品分类级联选择器
async getCateList(){
            const {data:res}=await this.$http.get('categories')

            if(res.meta.status!==200){
                return this.$message.error('获取商品分类失败')
            }
            this.catelist=res.data
        },
created(){
        this.getCateList()
    },
catelist:[],
<el-form-item label="商品分类">
                            <el-cascader
                            v-model="addForm.goods_cat"
                            :options="catelist"
                            :props="cateProps"
                            @change="handleChange">
                            </el-cascader>
                        </el-form-item>

在addForm中添加goods_cat属性

addForm:{
                goods_name:'',
                goods_price:0,
                goods_weight:0,
                goods_number:0,
                // 商品所属的分类数组
                goods_cat:[]
            },
cateProps:{
                 expandTrigger: 'hover',
                 label:'cat_name',
                 value:'cat_id',
                 children:'children'
             }

element的步骤条修改样式_ico_202


只允许选中三级分类

handleChange(){
            if(this.addForm.goods_cat.length!==3){
                this.addForm.goods_cat=[]
                return
            }
        }
  • 如果是在第一页并且没有选择商品分类则阻止标签页切换
<el-tabs :tab-position="'left'" v-model="activeIndex" :before-leave="beforeTabLeave" >
beforeTabLeave(activeName,oldActiveName){
            // activeName 即将进入的标签页名
            // oldActiveName 即将离开的标签页名

            // 阻止标签页切换
            if(oldActiveName=='0'&&this.addForm.goods_cat.length!==3){
                 this.$message.error('请先选择商品分类')
                return false
            }
            
           
        }
  • 进入商品参数标签页后获取动态参数列表数据

首先在切换标签页时判断访问的是动态参数面板
监听标签页的切换

<el-tabs :tab-position="'left'" v-model="activeIndex"
                 :before-leave="beforeTabLeave" @tab-click="tabClicked" >

证明访问的是动态参数面板(activeIndex===‘1’),获取动态参数列表数据

//  动态参数列表数据
            manyTableData:[]
async tabClicked(){
            // 证明访问的是动态参数面板
            if(this.activeIndex==='1'){
              const {data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,{
                  params:{sel:'many'}
              })
              if(res.meta.status!==200){
                  return this.$message.error('获取动态参数列表失败')
              }
              this.manyTableData=res.data
            }
        }

用computed计算goods_cat的值

computed:{
        cateId(){
            if(this.addForm.goods_cat.length===3){
                return this.addForm.goods_cat[2]
            }
            return null
        }
    }
  • 绘制商品参数面板中的复选框
    循环渲染出每一个label项 其中label项是指分类参数名称
<el-tab-pane label="商品参数" name="1">
                        <el-form-item :label="item.attr_name" v-for="item in manyTableData" :key="item.attr_id">

                        </el-form-item>
                    </el-tab-pane>

将attr_vals分割成数组 并且判断attr_vals是否是否为空 防止分割成含有空字符串的数组

// 证明访问的是动态参数面板
            if(this.activeIndex==='1'){
              const {data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,{
                  params:{sel:'many'}
              })
              if(res.meta.status!==200){
                  return this.$message.error('获取动态参数列表失败')
              }

              res.data.forEach(item=>{
                  item.attr_vals=item.attr_vals.length===0?[]:item.attr_vals.split(' ')
              })
              this.manyTableData=res.data
              
            }

渲染复选框组

<el-tab-pane label="商品参数" name="1">
                        <el-form-item :label="item.attr_name" v-for="item in manyTableData" :key="item.attr_id">
                              <!-- 复选框组 -->
                              <el-checkbox-group v-model="item.attr_vals">
                                <el-checkbox border :label="cb" v-for="(cb,i) in item.attr_vals" :key="i">
                                </el-checkbox>         
                            </el-checkbox-group>
                        </el-form-item>
                    </el-tab-pane>

element的步骤条修改样式_vue.js_203


将一个复选框的构取消时 对应的复选框会消失 并且attr_vals也会减少一项数据

  • 获取静态属性列表数据
// 证明访问的是静态属性面板
            else if(this.activeIndex==='2'){
                const {data:res}=await this.$http.get(`categories/${this.cateId}/attributes`,{
                  params:{sel:'only'}
              })
              if(res.meta.status!==200){
                  return this.$message.error('获取动态参数列表失败')
              }
              this.onlyTableDate=res.data
            }
// 静态属性列表数据
            onlyTableDate:[]
<el-tab-pane label="商品属性" name="2">
                        <el-form-item :label="item.attr_name" v-for="item in onlyTableDate" :key="item.attr_id">
                            <el-input v-model="item.attr_vals"></el-input>
                        </el-form-item>
                    </el-tab-pane>

element的步骤条修改样式_element的步骤条修改样式_204

  • 初步使用upload上传组件
<el-tab-pane label="商品图片" name="3">
                        <!-- action:表示图片要上传到的后台api地址 -->
                        <!-- on-preview:处理图片预览效果 -->
                        <!-- on-remove:处理图片关闭事件 -->
                        <!-- list-type:指定当前预览组件的呈现的方式  -->
                        <el-upload
                        :action="uploadURL"
                        :on-preview="handlePreview"
                        :on-remove="handleRemove"             
                        list-type="picture">
                            <el-button size="small" type="primary">点击上传</el-button>
                            <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
                        </el-upload>
                    </el-tab-pane>
// 上传图片的url地址
            uploadURL:'http://127.0.0.1:8888/api/private/v1/upload'
handlePreview(){

        },
        handleRemove(){

        }

element的步骤条修改样式_element的步骤条修改样式_205

  • 解决图片上传失败 token值无效的问题

由于除了登录 其他任何请求都需要提供token值 该组件使用的是内部所封装的发ajax请求的功能 并没有通过自己所设置的拦截器来添加token值 所以需要手动为其设置请求头添加token值
添加headers属性设置请求头

<el-upload
                        :headers="headerObj"
// 图片上传组件的headers请求头对象
            headerObj:{
                Authorization:window.sessionStorage.getItem('token')
            }

element的步骤条修改样式_vue.js_206

element的步骤条修改样式_html5_207


根据api接口 添加商品的请求数据包含一个上传的图片临时路径(对象)

element的步骤条修改样式_javascript_208

监听图片上传成功的事件 on-success

<el-upload
                        :headers="headerObj"
                        :action="uploadURL"
                        :on-preview="handlePreview"
                        :on-remove="handleRemove"             
                        list-type="picture"
                        :on-success="handleSuccess">
handleSuccess(responce){ //responce 服务器返回的数据对象
            console.log(responce)     
        }

此时打印出服务器返回的数据对象

element的步骤条修改样式_ico_209


tmp_path:临时路径

在添加商品的表单数据对象中添加一个pics 数组

// 添加商品的表单数据对象
            addForm:{
                goods_name:'',
                goods_price:0,
                goods_weight:0,
                goods_number:0,
                // 商品所属的分类数组
                goods_cat:[],
                pics:[]
            },

在handleSuccess方法中首先拼接得到一个图片信息对象
在将图片信息对象push到 pics数组

handleSuccess(responce){ //responce 服务器返回的数据对象
             
            // 拼接得到一个图片信息对象
            const picInfo={pic:responce.data.tmp_path}
            // 将图片信息对象push到 pics数组
            this.addForm.pics.push(picInfo)
        }

element的步骤条修改样式_javascript_210

  • 移除图片

监听upload组件的on-remove事件

handleRemove(file){
            console.log(file)
        },

监听移除图片事件,打印出将要移除的图片的信息对象

element的步骤条修改样式_javascript_211


实现移除功能

handleRemove(file){
            
            // 1 获取将要删除的图片临时路径
            const filePath=file.response.data.tmp_path
            // 2 从pics数组中 找到这个图片对应的索引值
            const i=this.addForm.pics.findIndex(item=>item.pic===filePath)
            // 3 调用数组数组的splice方法 把图片信息对象 从pics数组中移除
            this.addForm.pics.splice(i,1)
            console.log(this.addForm)

        }

element的步骤条修改样式_ico_212


element的步骤条修改样式_vue.js_213


element的步骤条修改样式_html5_214

  • 添加实现图片预览效果
<!-- 图片预览 -->
            <el-dialog
            title="预览"
            :visible.sync="imgDialogVisible"
            width="40%"
            >
            <div class="imgbox">
                 <img :src="previewPath" class="preview">
            </div>
           
            </el-dialog>
previewPath:'',
            imgDialogVisible:false
handlePreview(file){
            // 此时应该获取完整路径 url 而不是tmp_path(路径不完整)
            this.previewPath=file.response.data.url
            this.imgDialogVisible=true
        },

element的步骤条修改样式_vue.js_215

  • 实现商品详情页的富文本编辑器

将正在运行的网页关闭 停止项目运行 安装vue-quill-editor运行依赖
在main.js导入

// 导入富文本编辑器
import VueQuillEditor from 'vue-quill-editor'
Vue.use(VueQuillEditor)
// 富文本编辑器对应的样式
import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css'

在data的addForm添加商品详情属性

addForm:{
                goods_name:'',
                goods_price:0,
                goods_weight:0,
                goods_number:0,
                // 商品所属的分类数组
                goods_cat:[],
                pics:[],
                // 商品的详情描述
                goods_introduce:''
            },

根据该插件提供的使用文档的spa说明模块来添加该插件

<el-tab-pane label="商品内容" name="4">
                        <!-- 富文本编辑器组件 -->
                        <quill-editor v-model="addForm.goods_introduce"></quill-editor>
                        <el-button type="primary" class="btn_add">添加商品</el-button>
                    </el-tab-pane>

在global.css设置该编辑器高度

.ql-editor{
    min-height: 300px;
}

element的步骤条修改样式_ico_216

  • 实现表单数据预验证
<el-button type="primary" class="btn_add" @click="add">添加商品</el-button>
// 添加商品
        add(){
            this.$refs.addRuleForm.validate(valid=>{
                if(!valid){
                    return this.$message.error('请填写必要的表单项')
                }
            })
        }
  • 在发起请求前要将addForm.goods_cat属性转为字符串

注意:直接将其转为字符串 在点击添加商品时会报错
因为级联选择器绑定的数据源也为addForm.goods_cat
而级联选择器中有规定 v-model绑定的数据源必须为数组
所以我们需要 安装lodash运行依赖 利用该插件先将addForm深拷贝 得到一个新对象 将该对象转为字符串 这样就不会影响原本addForm中的数据

导入

import _ from 'lodash'
export default {
add(){
            this.$refs.addRuleForm.validate(valid=>{
                if(!valid){
                    return this.$message.error('请填写必要的表单项')
                }
                // 执行添加的业务逻辑
                // 深拷贝
                const form=_.cloneDeep(this.addForm)
                form.goods_cat=form.goods_cat.join(',')
            })
        }
  • 处理attrs数组

根据api接口文档需要传一个attrs参数

element的步骤条修改样式_vue.js_217


element的步骤条修改样式_element的步骤条修改样式_218


首先在addForm新增attrs属性

addForm:{
                goods_name:'',
                goods_price:0,
                goods_weight:0,
                goods_number:0,
                // 商品所属的分类数组
                goods_cat:[],
                pics:[],
                // 商品的详情描述
                goods_introduce:'',
                attrs:[]
            },

分别遍历 动态参数和静态属性的数组 拼接出包含attr_id和attr_value的对象 将该数据对象依次添加到addForm的attrs数组中 由于我们添加商品所上传的数据对象是form 所以还有将addForm.attrs赋值给form.attrs

// 添加商品
        add(){
            this.$refs.addRuleForm.validate(valid=>{
                if(!valid){
                    return this.$message.error('请填写必要的表单项')
                }
                // 执行添加的业务逻辑
                // 深拷贝
                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的类型要为字符串类型
                        attr_value:item.attr_vals.join(' ')
                    }
                    this.addForm.attrs.push(newInfo)

                })
                this.onlyTableDate.forEach(item=>{
                    const newInfo={attr_id:item.attr_id,
                    attr_value:item.attr_vals}
                    this.addForm.attrs.push(newInfo)
                })
                form.attrs=this.addForm.attrs
                console.log(form)
            })
        }

element的步骤条修改样式_vue.js_219

  • 完成添加商品

注意:如果在add函数内部直接写发起请求的相关代码 就会报错而 如果将发起请求的代码重新封装成一个函数 接着在add方法中调用就不会报错
以下报错:Parsing error: Can not use keyword ‘await’ outside an async function

所以只能如下

// 添加商品
         add(){
            this.$refs.addRuleForm.validate(valid=>{
                if(!valid){
                    return this.$message.error('请填写必要的表单项')
                }
                // 执行添加的业务逻辑
                // 深拷贝
                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的类型要为字符串类型
                        attr_value:item.attr_vals.join(' ')
                    }
                    this.addForm.attrs.push(newInfo)

                })
                this.onlyTableDate.forEach(item=>{
                    const newInfo={attr_id:item.attr_id,
                    attr_value:item.attr_vals}
                    this.addForm.attrs.push(newInfo)
                })
                form.attrs=this.addForm.attrs
                
                // 发起请求添加商品 商品的名称必须是唯一的(会报错)
                this.getPost(form)
               
            })
        }
async getPost(form){
              const {data:res}=await this.$http.post('goods',form)
                if(res.meta.status!==201){
                    console.log(res.meta)
                    return this.$message.error('添加商品失败')
                }
                this.$message.success('添加商品成功')
                // 路由导航回到商品列表
                this.$router.push('/goods')
        }

git提交代码

订单列表

新建一个order子分支 并上传到github

新建order目录 建立Order.vue 初始化界面 并创建对应的路由关系

  • 获取订单列表数据
export default {
    data(){
        return {
            queryInfo:{
                query:'',
                pagenum:1,
                pagesize:10
            },
            total:0,
            orderList:[]
        }
    },
    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('获取订单列表失败')
            }
            this.orderList=res.data.goods
            this.total=res.data.total
        }
    }
}

渲染订单列表

<el-card>
            <el-row>
                <el-col :span="8">
                    <el-input placeholder="请输入内容" class="input-with-select">
                        <el-button slot="append" icon="el-icon-search"></el-button>
                    </el-input>
                </el-col>
            </el-row>

            <el-table :data="orderList" border stripe>
                <el-table-column type="index"></el-table-column>
                <el-table-column label="订单编号" prop="order_number"></el-table-column>
                <el-table-column label="订单价格" prop="order_price"></el-table-column>
                <el-table-column label="是否付款" prop="pay_status">
                    <template slot-scope="scope">
                        <el-tag type="success" v-if="scope.row.order_pay==='1'?true:false">已付款</el-tag>
                        <el-tag type="danger" v-else>未付款</el-tag>
                    </template>
                </el-table-column>
                <el-table-column label="是否发货" prop="is_send">
                    <template slot-scope="scope">
                        {{scope.row.is_send}}
                    </template>
                </el-table-column>
                <el-table-column label="下单时间" prop="create_time">
                    <template slot-scope="scope">
                        {{scope.row.create_time|dataFormat}}
                    </template>
                </el-table-column>
                <el-table-column label="操作" >
                    <template >
                        <el-button size="mini" type="primary" icon="el-icon-edit"></el-button>
                        <el-button size="mini" type="success" icon="el-icon-location"></el-button>
                    </template>
                </el-table-column>
            </el-table>
                
            
        </el-card>
    </div>

实现分页

<el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current-page="queryInfo.pagenum"
                :page-sizes="[5, 10, 15]"
                :page-size="queryInfo.pagesize"
                layout="total, sizes, prev, pager, next, jumper"
                :total="total">
              </el-pagination>
handleSizeChange(newSize){
            this.queryInfo.pagesize=newSize
            this.getOrderList()
        },
        handleCurrentChange(newPage){
            this.queryInfo.pagenum=newPage
            this.getOrderList()
        }

element的步骤条修改样式_javascript_220

  • 实现修改地址 弹出修改地址对话框 在对话框内部实现省市数据联动

导入城市数据 并将数据放到data中

import cityData from '../../citydata.js'
addressDialogVisible:false,
            addressForm:{
                address1:[],
                address2:''
            },
            // 表单验证
            addressFormRules: {
            address1: [
                    { required: true, message: '请选择省市区/县', trigger: 'blur' },
                
                ],
            address2: [
                    { required: true, message: '请输入详细地址', trigger: 'blur' },
                
                ],    
             },
            cityData,
<el-dialog
                title="修改地址"
                :visible.sync="addressDialogVisible"
                width="40%"
                @close="addressClosed">
                <el-form :model="addressForm" :rules="addressFormRules" ref="addressRuleFormRef" label-width="100px" class="demo-ruleForm">
                    <el-form-item label="省市区/县" prop="address1">
                        <!--实现数据联动-->
                        <el-cascader :options="cityData" v-model="addressForm.address1"></el-cascader>
                    </el-form-item>
                    <el-form-item label="详细地址" prop="address2">
                        <el-input v-model="addressForm.address2"></el-input>
                    </el-form-item>
                </el-form>    
                <span slot="footer" class="dialog-footer">
                    <el-button @click="addressDialogVisible = false">取 消</el-button>
                    <el-button type="primary" @click="addressDialogVisible = false">确 定</el-button>
                </span>
        </el-dialog>

给编辑按钮绑定事件 弹出对话框

<el-button size="mini" type="primary" icon="el-icon-edit"  @click="showBox"></el-button>
showBox(){
            // console.log(1)
            this.addressDialogVisible=true
        },

点击关闭重置表单

addressClosed(){
            this.$refs.addressRuleFormRef.resetFields()
        },

element的步骤条修改样式_html5_221

  • 点击位置按钮显示快递进度

导入并注册element-ui的timeline组件

弹出对话框并获取物流信息

<el-button size="mini" type="success" icon="el-icon-location" @click="showProgress"></el-button>
progressVisible:false,
 progressInfo:[]
async showProgress(){
            const {data:res}=await this.$http.get('/kuaidi/804909574412544580')
            if(res.meta.status!==200){
                
                return this.$message.error('获取物流进度失败')
            }
           
            this.progressInfo=res.data
            //  console.log(this.progressInfo)
            this.progressVisible=true
        }
<el-dialog
        title="物流进度"
        :visible.sync="progressVisible"
        width="40%"
       >
         <el-timeline >
            <el-timeline-item
            v-for="(activity, index) in progressInfo"
            :key="index"
            :timestamp="activity.time">
            {{activity.context}}
            </el-timeline-item>
        </el-timeline>
       
        </el-dialog>

element的步骤条修改样式_vue.js_222


上传提交orders分支代码

数据统计

建立文件夹report 新建report.vue组件 创建路由关系

  • 完成数据图表显示

安装echars运行依赖 并导入
根据官方文档 放置对应的组件

import echarts from 'echarts'
<el-card>
             <div id="main" style="width: 750px;height:400px;"></div>
        </el-card>

在mounted中初始化echars实例 并且获取图标数据 根据后台api文档说明 要将获取的数据和options对象合并 才能显示具体信息鼠标跟随显示的效果

element的步骤条修改样式_javascript_223


将后台提供的options数据放到data中

// 需要合并的数据
            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'
                }
                ]
            }
        }

导入lodash 调用merge方法将两个对象合并 形成一个新的对象

import _ from 'lodash'
// dom初始化完毕
    async mounted(){
        // 初始化echars实例
        var myChart = echarts.init(document.getElementById('main'));

        const {data:res}=await this.$http.get('reports/type/1')
        if(res.meta.status!==200){
            return this.$message.error('获取图表数据失败')
        }
        // 准备数据源和配置项
        const result=_.merge(res.data,this.options)
        // 显示数据
        myChart.setOption(result)
    },

element的步骤条修改样式_javascript_224


鼠标跟随效果

element的步骤条修改样式_html5_225


将代码上传github

项目优化和上线

element的步骤条修改样式_html5_226

  • 安装NProgress运行依赖 实现页面加载时的进度条效果

Main.js

//导入NProgress 包对应的JS和CSS
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

分别在axios设置拦截器的发起请求和响应请求中调用start()显示 和done()隐藏方法

//设置拦截器
axios.interceptors.request.use(config=>{
  
  config.headers.Authorization=window.sessionStorage.getItem('token')
  NProgress.start()
  //在最后必须return config
  return config
})
axios.interceptors.response.use(config=>{
  NProgress.done()
  return config
})
  • 只在发布阶段移除所有的console

安装transform-remove-console运行依赖

在vue可视化面板 在运行时可以看到 --mode 后面输出的值为development

element的步骤条修改样式_html5_227


在编译阶段可以看到输出的值为

element的步骤条修改样式_javascript_228


这两个值分别代表开发阶段和发布阶段

在babel.config.js中定义一个数组表示这是在项目发布阶段用到的插件 接着获取以上的那两个值 判断是否在发布阶段 如果在发布阶段就将transform-remove-console 插件放到该数组中

// 这是在项目发布阶段用到的插件
const prodPlugins=[]
if(process.env.NODE_ENV==='production'){
  prodPlugins.push('transform-remove-console')
}

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    [
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ],
    // 展开运算符放置发布产品时候的插件数组
    ...prodPlugins
  ]
}
  • 生成打包报告

element的步骤条修改样式_ico_229


我们可以发现有些资源和依赖项体积太大 此时需要做进一步的优化

element的步骤条修改样式_vue.js_230

  • 优化

element的步骤条修改样式_javascript_231


新建一个vue.config.js配置文件

element的步骤条修改样式_element的步骤条修改样式_232


element的步骤条修改样式_vue.js_233


element的步骤条修改样式_vue.js_234


element的步骤条修改样式_vue.js_235


将自己项目的main.js文件重命名为main-dev.js 并复制其中代码 在新建一个main-prod.js 将代码粘贴到其中 指定两个不同的打包入口文件

在vue.config.js中输入

module.exports={
    chainWebpack:config=>{
        // 判断所处的是哪一种模式
        // 发布阶段 打包默认入口修改
        config.when(process.env.NODE_ENV==='production',config=>{
            // 调用clear()清空默认的打包入口文件main.js 在调用add 追加自己新建的打包入口
            config.entry('app').clear().add('./src/main-prod.js')
        })

        config.when(process.env.NODE_ENV==='development',config=>{
            // 调用clear()清空默认的打包入口文件main.js 在调用add 追加自己新建的打包入口
            config.entry('app').clear().add('./src/main-dev.js')
        })
    }

}

在vue面板中重新编译并运行

  • 通过externals 加载CDN外部资源

element的步骤条修改样式_vue.js_236


此时我们可以发现chunk-venders.js很大 原因是将那些依赖项都打包了这个文件中

element的步骤条修改样式_element的步骤条修改样式_237


element的步骤条修改样式_html5_238


在发布阶段添加如下代码

config.when(process.env.NODE_ENV==='production',config=>{
            // 调用clear()清空默认的打包入口文件main.js 在调用add 追加自己新建的打包入口
            config.entry('app').clear().add('./src/main-prod.js')

            config.set('externals',{
                vue:'Vue',
                "vue-router":"VueRouter",
                axios:'axios',
                lodash:'_',
                echarts:'echarts',
                nprogress:'NProgress',
                "vue-quill-editor":'VueQuillEditor'
            })
        })

element的步骤条修改样式_javascript_239


将main-prod.js 中 引入的富文本编辑器和NProgress样式文件删除

import 'quill/dist/quill.core.css' // import styles
import 'quill/dist/quill.snow.css' // for snow theme
import 'quill/dist/quill.bubble.css'
import 'nprogress/nprogress.css'

将样式文件直接放到public/index.html中

<!-- noprogress的样式表文件 -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css"/>
    <!-- 富文本编辑器的样式表文件 -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.core.min.css"/>
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.snow.min.css"/>
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.bubble.min.css"/>

element的步骤条修改样式_html5_240

这些js文件也正是刚才在config.js中设置的那些文件

<script src="https://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script>
    <script src="https://cdn.staticfile.org/vue-router/3.3.2/vue-router.min.js"></script>
    <script src="https://cdn.staticfile.org/axios/0.19.2/axios.min.js"></script>
    <script src="https://cdn.staticfile.org/lodash.js/4.17.19/lodash.min.js"></script>
    <script src="https://cdn.staticfile.org/echarts/4.8.0/echarts.min.js"></script>
    <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
    <!-- 富文本编辑器的 js 文件 -->
    <script src="https://cdn.staticfile.org/quill/1.3.4/quill.min.js"></script>
    <script src="https:///npm/vue-quill-editor@3.0.6/dist/vue-quill-editor.js"></script>

此时编译 可能会出现以下错误
ERROR Error: No module factory available for dependency type: CssDependency

Error: No module factory available for dependency type: CssDependency
at addDependency (C:\Users\user\Desktop\heima39\vue_shop\node_modules\webpack\lib\Compilation.js:800:12)
at iterationOfArrayCallback
Error: Callback was already called.
at throwError (C:\Users\user\Desktop\heima39\vue_shop\node_modules\neo-async\async.
js:16:11)
at C:\Users\user\Desktop\heima39\vue_
shop\node_modules\neo-async\async.js:2818:7
at processTicksAndRej

需要在config.js中配置

module.exports={
    css:{
        extract:false
    },

此时编译结果 可以发现chunk-vanders体积明显减少 并且之前的echarts等依赖性也没有出现在右侧中

element的步骤条修改样式_html5_241

  • 通过CDN优化ElementUI这些组件的打包

此时第一个js文件的体积还是过大 主要是element-ui占了绝大空间

element的步骤条修改样式_vue.js_242


此时将main-prod.js中导入element.js注释

// import './plugins/element.js'

在index.html中添加

<!-- element-ui 的样式表文件 -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.13.2/theme-chalk/index.css" />
<!-- element-ui 的 js 文件 -->
     <script src="https://cdn.staticfile.org/element-ui/2.13.2/index.js"></script>

再次build编译 此时vander.js和app的文件大小只有100多kb了

element的步骤条修改样式_element的步骤条修改样式_243

  • 定制首页内容

在开发模式下打开页面会显示-dev 电商管理系统 而在发布模式下则不会显示

element的步骤条修改样式_javascript_244


element的步骤条修改样式_javascript_245


element的步骤条修改样式_vue.js_246


在config.js的不同模式中配置 config.plugin(‘html’)

chainWebpack:config=>{
        
        // 判断所处的是哪一种模式
        // 发布阶段 打包默认入口修改
        config.when(process.env.NODE_ENV==='production',config=>{
            // 调用clear()清空默认的打包入口文件main.js 在调用add 追加自己新建的打包入口
            config.entry('app').clear().add('./src/main-prod.js')

            config.set('externals',{
                vue:'Vue',
                "vue-router":"VueRouter",
                axios:'axios',
                lodash:'_',
                echarts:'echarts',
                nprogress:'NProgress',
                "vue-quill-editor":'VueQuillEditor'
            })

            config.plugin('html').tap(args=>{
                args[0].isProd=true
                return args
            })

            
        })

        config.when(process.env.NODE_ENV==='development',config=>{
            // 调用clear()清空默认的打包入口文件main.js 在调用add 追加自己新建的打包入口
            config.entry('app').clear().add('./src/main-dev.js')

            
            config.plugin('html').tap(args=>{
            args[0].isProd=false
            return args
          })
        })

    }

在index.html中根据isProd的值是否为true来显示标题是否要带dev

<title><%= htmlWebpackPlugin.options.isProd ? '':'dev - '%>电商后台管理系统</title>

并且通过if来根据isProd的值显示所引入的文件 如果是在发布阶段则显示这些文件

<% if(htmlWebpackPlugin.options.isProd){ %>
    <!-- noprogress的样式表文件 -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css"/>
    <!-- 富文本编辑器的样式表文件 -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.core.min.css"/>
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.snow.min.css"/>
    <link rel="stylesheet" href="https://cdn.staticfile.org/quill/1.3.4/quill.bubble.min.css"/>
    <!-- element-ui 的样式表文件 -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.13.2/theme-chalk/index.css" />

    <script src="https://cdn.staticfile.org/vue/2.6.11/vue.min.js"></script>
    <script src="https://cdn.staticfile.org/vue-router/3.3.2/vue-router.min.js"></script>
    <script src="https://cdn.staticfile.org/axios/0.19.2/axios.min.js"></script>
    <script src="https://cdn.staticfile.org/lodash.js/4.17.19/lodash.min.js"></script>
    <script src="https://cdn.staticfile.org/echarts/4.8.0/echarts.min.js"></script>
    <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
    <!-- 富文本编辑器的 js 文件 -->
    <script src="https://cdn.staticfile.org/quill/1.3.4/quill.min.js"></script>
    <script src="https:///npm/vue-quill-editor@3.0.6/dist/vue-quill-editor.js"></script>

     <!-- element-ui 的 js 文件 -->
     <script src="https://cdn.staticfile.org/element-ui/2.13.2/index.js"></script>

     <% } %>

此时在vue面板中server运行 可以看见 在开发模式 带了-dev前缀

element的步骤条修改样式_javascript_247

  • 路由懒加载

import中后面的红色字体部分 表示路由真正存放的路径 前面类似于注释的部分表示路由所属的组 位于同一个组的路由会被打包到同一个js文件中 并且会同时请求

element的步骤条修改样式_vue.js_248


安装@babel/plugin-syntax-dynamic-import开发依赖(注意是:开发依赖)

在babel.config.js中配置 @babel/plugin-syntax-dynamic-import

// 这是在项目发布阶段用到的插件
const prodPlugins=[]
if(process.env.NODE_ENV==='production'){
  prodPlugins.push('transform-remove-console')
}

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    [
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ],
    // 展开运算符放置发布产品时候的插件数组
    ...prodPlugins,
    "@babel/plugin-syntax-dynamic-import"
  ]
}

将index.js中的部分导入路由形式改造成懒加载形式导入

import Vue from 'vue'
import VueRouter from 'vue-router'

// import login from '../components/Login.vue'
// import home from '../components/Home.vue'
// import welcome from '../components/Welcome.vue'
// import users from '../components/user/Users.vue'
// import rights from '../components/power/Rights.vue'
// import roles from '../components/power/Roles.vue'
// import cates from '../components/goods/Cates.vue'
// import params from '../components/goods/Params.vue'
// import list from '../components/goods/List.vue'
// import add from '../components/goods/Add.vue'
// import order from '../components/order/Order.vue'
// import report from '../components/report/Report.vue'

// 懒加载形式导入路由组件
const login=()=>import(/* webpackChunkName: "login_home_welcome" */ '../components/Login.vue' )
const home=()=>import(/* webpackChunkName: "login_home_welcome" */ '../components/Home.vue' )
const welcome=()=>import(/* webpackChunkName: "login_home_welcome" */ '../components/Welcome.vue' )

const users=()=>import(/* webpackChunkName: "users_rights_roles" */ '../components/user/Users.vue' )
const rights=()=>import(/* webpackChunkName: "users_rights_roles" */ '../components/power/Rights.vue' )
const roles=()=>import(/* webpackChunkName: "users_rights_roles" */ '../components/power/Roles.vue' )

const cates=()=>import(/* webpackChunkName: "cates_params" */ '../components/goods/Cates.vue' )
const params=()=>import(/* webpackChunkName: "cates_params" */ '../components/goods/Params.vue' )

const list=()=>import(/* webpackChunkName: "list_add" */ '../components/goods/List.vue' )
const add=()=>import(/* webpackChunkName: "list_add" */ '../components/goods/Add.vue' )

const order=()=>import(/* webpackChunkName: "order_report" */ '../components/order/Order.vue' )
const report=()=>import(/* webpackChunkName: "order_report" */ '../components/report/Report.vue' )

此时build编译 会发现原来的app.js和chunk-vander.js体积更小了

element的步骤条修改样式_vue.js_249

  • 项目上线

element的步骤条修改样式_vue.js_250


element的步骤条修改样式_vue.js_251


新建vue_shop_目录 将原来的vue_shop文件剪切到其中 在该目录中在新建vue_shop_server 并用npm init -y 初始化

element的步骤条修改样式_javascript_252


安装express包

npm i express -S将vue-shop生成的dist包放到该文件夹中

element的步骤条修改样式_vue.js_253


新建app.js文件

const express=require('express')
const app=express()

app.use(express.static('./dist'))

app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

node app.js运行该文件 在浏览器打开http://127.0.0.1即可

  • 开启文件的Gzip网络格式压缩

element的步骤条修改样式_vue.js_254


安装 npm i compression -S

const express=require('express')
const app=express()

const compression=require('compression')

// 一定要把这一行代码 写道静态资源托管之前
app.use(compression())

app.use(express.static('./dist'))

app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

此时重新运行可以发现 在网络请求资源中chunk-vander.js被压缩到只要20多k

element的步骤条修改样式_javascript_255

  • 配置https服务(一般作为后台开发人员经行)

element的步骤条修改样式_javascript_256


element的步骤条修改样式_element的步骤条修改样式_257


element的步骤条修改样式_vue.js_258


安装compression

npm i compression -S将生成的公钥和私钥文件放到 该文件夹下

element的步骤条修改样式_element的步骤条修改样式_259


app.js

const express=require('express')
const app=express()
const compression=require('compression')
const https=require('https')
const fs=require('fs')

const options={
    cert:fs.readFileSync('./full_chain.pem'),
    key:fs.readFileSync('./private.key')
}

const compression=require('compression')

// 一定要把这一行代码 写道静态资源托管之前
app.use(compression())

app.use(express.static('./dist'))

// app.listen(80,()=>{
//     console.log('server running at http://127.0.0.1')
// })

// https创建服务器 https协议的网站默认运行在443端口
https.createServer(options,app).listen(443)
  • 使用pm2管理应用

此时我们还是将项目运行到本机

app.listen(80,()=>{
    console.log('server running at http://127.0.0.1')
})

// https创建服务器 https协议的网站默认运行在443端口
// https.createServer(options,app).listen(443)

此时运行node app.js 打开http://127.0.0.1是可以访问的 但是在关闭运行终端以后服务就会被关闭网站就不能被打开 此时需要启用pm2实现再关闭终端后也能访问

element的步骤条修改样式_html5_260

安装
npm i pm2 -g

element的步骤条修改样式_html5_261


此时关闭终端也能运行通过pm2 ls 可以查看运行状态

element的步骤条修改样式_ico_262


停止项目 后面可跟随项目id

element的步骤条修改样式_element的步骤条修改样式_263