欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue速成--项目实战(后台管理系统)

程序员文章站 2022-06-01 22:41:52
...

Vue速成--项目实战(后台管理系统)

需要后端服务器资料的童鞋,私信留言哦~

umall 后台管理

1.创建项目

vue init webpack umall

2.清空工作

1.assets 清空

2.components 清空

3.router/index.js 删除helloword相关的

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

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

4.App.vue重置

3.项目搭建

1.目录结构

-src
	-assets 静态资源
		-css 
		-js 
	-components 公共组件
		index.js  整合公共组件
	-filters 过滤器
		index.js 整合过滤器
	-pages 路由组件
		-home 某个路由组件
			home.vue  路由组件
			-components 路由组件的子组件
				banner.vue
				list.vue
	-router 路由
		index.js
	-store 仓库
		index.js 创建仓库并导出
		mutations.js 根级别下 的state mutations getters
		acions.js 根级别下的actions
		-modules 模块
	-utils 工具类
    	alert.js 弹框
    	request.js 数据交互
    App.vue 根组件
    main.js 入口文件

2.assets

css /reset.css,在main.js引入

//1.assets 
import "./assets/css/reset.css"

3.公共组件 components

components/index.js

import Vue from "vue"

let obj={
    
}
for(let i in obj){
    Vue.component(i,obj[i])
}

4.路由组件的格式

-pages
	-home 
		home.vue 
		-components
			banner.vue
			list.vue
	-detail 
		detail.vue
		-components
			picker.vue

5.数据交互

1.安装依赖包
npm i axios qs --save
2.配置代理 config/index.js
proxyTable: {
      "/api":{
        target:"http://localhost:3000",
        changeOrigin:true,
        pathRewrite:{
          "^/api":"http://localhost:3000"
        }
      }
    },
3.utils/request.js
import axios from "axios"
import qs from "qs"

let baseUrl="/api";

//响应拦截
axios.interceptors.response.use(res=>{
    console.group("=====本次请求路径是:"+res.config.url)
    console.log(res);
    console.groupEnd()

    return res;
})


6.过滤器filters

filters/index.js

import Vue from "vue"

let obj={
    
}
for(let i in obj){
    Vue.filter(i,obj[i])
}


7.vuex

1.安装依赖包
npm i vuex --save

2.目录搭建
-store 仓库
		index.js 创建仓库并导出
		mutations.js 根级别下 的state mutations getters
		acions.js 根级别下的actions
		-modules 模块

3.store/index.js
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
import {state,getters,mutations} from "./mutations"
import {actions} from "./actions"
export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions,
    modules:{
        
    }
})

store/mutations.js

export const state={}
export const mutations={}
export const getters={}

store/actions.js

export const actions={}

4.main.js引入store,并挂到根实例上
//6.vuex 
import store from "./store"

new Vue({
  el: '#app',
 
  store,
  
})

8.element-ui

1.安装依赖包
npm i element-ui --save

2.main.js引入
//7.element-ui
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);


3.二次封装弹框 utils/alert.js
import Vue from "vue"
let vm = new Vue()
export const successAlert = (msg) => {
    vm.$message({
        message: msg,
        type: 'success'
    });
}
export const warningAlert = (msg) => {
    vm.$message({
        message: msg,
        type: 'warning'
    });
}

4.配置一级路由

1.pages下创建了index 和login组件

2.router/index.js 配置路由规则

export default new Router({
  routes: [
    {
      path:"/login",
      component:()=>import("../pages/login/login.vue")
    },
    {
      path:"/",
      component:()=>import("../pages/index/index.vue")
    }
  ]
})


3.app.vue 定义路由出口

  <div>
    <router-view></router-view>
  </div>

4.书写了一个用户代码片段

{
	
	"Print to console": {
		"scope": "javascript,typescript,vue",
		"prefix": "vue",
		"body": [
			"<template>",
			"<div>",
			"<h1>登录</h1>",
            "</div>",
            "</template>",

            "<script>",
            "import {mapGetters,mapActions} from \"vuex\"",
            "export default {",
            "props:[],",
            "components:{},",
            "data(){",
            "return {",

            "}",
            "},",
            "computed:{",
            "...mapGetters({",

            "})",
            "},",
            "methods:{",
            "...mapActions({",

            "})",
            "},",
            "mounted(){},",

            "}",
            "</script>",

            "<style scoped>",

            "</style>"
		],
		"description": "我的vue"
	}
}

5.login.vue

1.html+css

<div class="login">
      <div class="box">
        <h3 class="center">登录</h3>
        <div class="line">
          <el-input placeholder="情输入账号" clearable></el-input>
        </div>
        <div class="line">
          <el-input placeholder="情输入密码" clearable show-password></el-input>
        </div>
        <div class="center line">
          <el-button type="primary" @click="login">登录</el-button>
        </div>
      </div>
  </div>
<style scoped>
.login{
  width: 100vw;
  height: 100vh;
  background: linear-gradient(to right,#553443,#303D60);
  position: relative;
}
.box{
  width: 400px;
  padding: 20px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%);
  background: #ffffff;
  border-radius: 20px;
}
.center{
  text-align: center;
}
.line{
  margin-top: 20px;
}
</style>

6.index.vue

1.粘贴布局容器
  <el-container class="index">
    <el-aside class="left" width="200px">
      
    </el-aside>
    <el-container>
      <el-header class="header">Header</el-header>
      <el-main class="main">
        <!-- 二级路由出口 -->
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>

2.粘贴导航
<el-aside class="left" width="200px">
      <!-- 左侧导航 -->
      <!-- 1.background-color -->
      <el-menu
        default-active="2"
        class="el-menu-vertical-demo"
        @open="handleOpen"
        @close="handleClose"
        background-color="#20222a"
        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-group>
            <template slot="title">分组一</template>
            <el-menu-item index="1-1">选项1</el-menu-item>
            <el-menu-item index="1-2">选项2</el-menu-item>
          </el-menu-item-group>
          <el-menu-item-group title="分组2">
            <el-menu-item index="1-3">选项3</el-menu-item>
          </el-menu-item-group>
          <el-submenu index="1-4">
            <template slot="title">选项4</template>
            <el-menu-item index="1-4-1">选项1</el-menu-item>
          </el-submenu>
        </el-submenu>
        <el-menu-item index="2">
          <i class="el-icon-menu"></i>
          <span slot="title">导航二</span>
        </el-menu-item>
        <el-menu-item index="3" disabled>
          <i class="el-icon-document"></i>
          <span slot="title">导航三</span>
        </el-menu-item>
        <el-menu-item index="4">
          <i class="el-icon-setting"></i>
          <span slot="title">导航四</span>
        </el-menu-item>
      </el-menu>
    </el-aside>

3.el-main 定义二级路由出口
 <el-main class="main">
     <!-- 二级路由出口 -->
     <router-view></router-view>
</el-main>

4.创建二级路由组件
menu  菜单管理
role  角色管理
manage  管理员管理
cate  商品分类
specs  商品规格
goods 商品管理
member 会员管理
banner 轮播图管理
seckill 秒杀活动

5.配置路由规则 router/index.js
//首页下面的二级路由规则
let indexRoutes = [
  {
    path: "/menu",
    component: () => import("../pages/menu/menu.vue")
  },
  {
    path: "/role",
    component: () => import("../pages/role/role.vue")
  },
  {
    path: "/manage",
    component: () => import("../pages/manage/manage.vue")
  },
  {
    path: "/cate",
    component: () => import("../pages/cate/cate.vue")
  },
  {
    path: "/specs",
    component: () => import("../pages/specs/specs.vue")
  },
  {
    path: "/goods",
    component: () => import("../pages/goods/goods.vue")
  },
  {
    path: "/member",
    component: () => import("../pages/member/member.vue")
  },
  {
    path: "/banner",
    component: () => import("../pages/banner/banner.vue")
  },
  {
    path: "/seckill",
    component: () => import("../pages/seckill/seckill.vue")
  },
]

{
      path: "/",
      component: () => import("../pages/index/index.vue"),
      children: [
        {
          path: "",
          component: () => import("../pages/home/home.vue"),
        },
        ...indexRoutes
      ]
    }

6.实现侧边栏
 <el-aside class="left" width="200px">
      <!-- 左侧导航 -->
      <!-- 1.background-color 背景颜色 
          2.router 开启路由模式,index 是你想要跳转的路径
          3.unique-opened 保持只有一个子菜单展开
      -->

      <el-menu
        class="el-menu-vertical-demo"
        background-color="#20222a"
        text-color="#fff"
        active-text-color="#ffd04b"
        router
        unique-opened
      >
        <el-menu-item index="/">
          <i class="el-icon-menu"></i>
          <span slot="title">首页</span>
        </el-menu-item>
        <el-submenu index="2">
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>系统设置</span>
          </template>
          <el-menu-item-group>
            <el-menu-item index="/menu">菜单管理</el-menu-item>
            <el-menu-item index="/role">角色管理</el-menu-item>
            <el-menu-item index="/manage">管理员管理</el-menu-item>
          </el-menu-item-group>
        </el-submenu>
        <el-submenu index="3">
          <template slot="title">
            <i class="el-icon-location"></i>
            <span>商城管理</span>
          </template>
          <el-menu-item-group>
            <el-menu-item index="/cate">商品分类</el-menu-item>
            <el-menu-item index="/specs">商品规格</el-menu-item>
            <el-menu-item index="/goods">商品管理</el-menu-item>
            <el-menu-item index="/member">会员管理</el-menu-item>
            <el-menu-item index="/banner">轮播图管理</el-menu-item>
            <el-menu-item index="/seckill">秒杀活动</el-menu-item>
          </el-menu-item-group>
        </el-submenu>
      </el-menu>
    </el-aside>

7.添加了一个面包屑
	<el-main class="main">
        <!-- 面包屑 -->
        <el-breadcrumb v-if="$route.name" separator-class="el-icon-arrow-right">
          <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
          <el-breadcrumb-item>{{$route.name}}</el-breadcrumb-item>
        </el-breadcrumb>
        <!-- 二级路由出口 -->
        <router-view></router-view>
     </el-main>

8.给每一个二级路由规格添加了name属性 router/index.js
let indexRoutes = [
  {
    path: "/menu",
    name:"菜单管理",
    component: () => import("../pages/menu/menu.vue")
  },
  {
    path: "/role",
    name:"角色管理",
    component: () => import("../pages/role/role.vue")
  },
 ]

7.menu.vue

1.拆分menu.vue

将menu组件分为 添加按钮 list add

<template>
  <div>
    <el-button type="primary" @click="willAdd">添加</el-button>

    <!-- 表格 -->
    <v-list></v-list>

    <!-- 添加组件 -->
    <v-add :info="info"></v-add>
  </div>
</template>

2.通过模拟数据实现list.vue
<template>
  <el-table
    :data="tableData"
    style="width: 100%; margin-bottom: 20px"
    row-key="id"
    border
    default-expand-all
    :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
  >
    <el-table-column prop="date" label="日期" sortable width="180">
    </el-table-column>
    <el-table-column prop="name" label="姓名" sortable width="180">
    </el-table-column>
    <el-table-column prop="address" label="地址"> </el-table-column>
  </el-table>
</template>

 data() {
    return {
      tableData: [
        {
          id: 1,
          date: "2016-05-02",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
        {
          id: 2,
          date: "2016-05-04",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1517 弄",
        },
        {
          id: 3,
          date: "2016-05-01",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1519 弄",
          children: [
            {
              id: 31,
              date: "2016-05-01",
              name: "王小虎",
              address: "上海市普陀区金沙江路 1519 弄",
            },
            {
              id: 32,
              date: "2016-05-01",
              name: "王小虎",
              address: "上海市普陀区金沙江路 1519 弄",
            },
          ],
        },
        {
          id: 4,
          date: "2016-05-03",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1516 弄",
        },
      ],
    };
  },

3.add.vue

1.form

 <el-form ref="form" :model="form" label-width="80px">
        <el-form-item label="菜单名称">
          <el-input v-model="form.name"></el-input>
        </el-form-item>
        <el-form-item label="上级菜单">
          <el-select v-model="form.name" placeholder="请选择上级菜单">
            <el-option label="*菜单" value="shanghai"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="菜单类型">
          <el-radio v-model="form.name" label="1">目录</el-radio>
          <el-radio v-model="form.name" label="2">菜单</el-radio>
        </el-form-item>
        <el-form-item label="菜单图标">
          <el-select v-model="form.name" placeholder="请选择上级菜单">
            <el-option v-for="item in icons" :key="item" :value="item">
              <i :class="item"></i>
            </el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="菜单地址">
          <el-select v-model="form.name" placeholder="请选择上级菜单">
            <el-option
              v-for="item in indexRoutes"
              :key="item.path"
              :label="item.name"
              :value="'/' + item.path"
            >
            </el-option>
          </el-select>
        </el-form-item>

        <el-form-item label="状态">
          <el-switch v-model="form.name"></el-switch>
        </el-form-item>

      </el-form>

 data() {
    return {
      //图标
      icons: [
        "el-icon-s-tools",
        "el-icon-user",
        "el-icon-camera",
        "el-icon-message-solid",
      ],
      //路由
      indexRoutes: indexRoutes,
      form: {
        name: "",
      },
    };
  },

4.对话框
 	<el-dialog title="添加菜单" :visible.sync="info.isshow">
      <el-form ref="form" :model="form" label-width="80px">
       ...
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="cancel">取 消</el-button>
        <el-button type="primary">添 加</el-button
        >
      </div>
    </el-dialog>

对话框需要一个变量isshow来控制出现和消失,将数据isshow定义在menu.vue中,传递过来,但是dialog需要直接修改这个数据,所以不能传递简单类型,需要传递一个json格式的数据,才可以实现父变子变,子变 父变得要求。

menu.vue

 data() {
    return {
      //传递给add的数据
      info: {
        //添加弹框的状态
        isshow: false,
      },
    };
  },
  methods: {
    //点击了添加按钮
    willAdd() {
      this.info.isshow = true;
    },
  },

<v-add :info="info"></v-add>

add.vue通过props接收

{
	props:["info"]
}

<el-dialog :visible.sync="info.isshow"></el-dialog>

5.交互的接口 utils/request.js
//添加
export const reqMenuAdd=(params)=>{
    return axios({
        url:baseUrl+"/api/menuadd",
        method:"post",
        data:qs.stringify(params)
    })
}

//列表
export const reqMenuList=()=>{
    return axios({
        url:baseUrl+"/api/menulist",
        method:"get",
        params:{
            istree:true
        }
    })
}
//删除
export const reqMenuDel=(id)=>{
    return axios({
        url:baseUrl+"/api/menudelete",
        method:"post",
        data:qs.stringify({id:id})
    })
}

6.add.vue 添加交互

1.form的字段和后端保持一致,然后绑定到每个表单上

	form: {
        pid:0,
        title:"",
        icon:"",
        type:1,
        url:"",
        status:1
      },

2.点击了添加按钮,开始添加交互

	 //取消
    cancel(){
      this.info.isshow=false;
    },
    //数据重置
    empty(){
      this.form={
        pid:0,
        title:"",
        icon:"",
        type:1,
        url:"",
        status:1
      }
    },
	//点击了添加按钮
    add(){
      
      reqMenuAdd(this.form).then(res=>{
        if(res.data.code==200){
          //成功
          successAlert(res.data.msg)

          //数据重置
          this.empty()

          //弹框消失
          this.cancel()

          //list数据要刷新
          this.reqListAction()
        }else{
          warningAlert(res.data.msg)
        }
      })
    }

3.如果是 *菜单下,就是目录,目录有图标,没有地址;

如果不是*菜单下的,就是菜单,菜单没有图标。有地址

		<el-form-item label="上级菜单">
          <el-select v-model="form.pid" placeholder="请选择上级菜单" @change="changePid">
            ...
          </el-select>
        </el-form-item>
        <el-form-item label="菜单类型">
          <el-radio v-model="form.type" :label="1" disabled>目录</el-radio>
          <el-radio v-model="form.type" :label="2" disabled>菜单</el-radio>
        </el-form-item>
        <el-form-item label="菜单图标" v-if="form.type==1">
         ...
        </el-form-item>
        <el-form-item label="菜单地址" v-else>
          ...
        </el-form-item>

 //修改了pid
    changePid(){
      if(this.form.pid==0){
        this.form.type=1
      }else{
        this.form.type=2
      }
    },

7.获取菜单的list

由于菜单的list的数据,很多地方都要用到。所以将他存在状态层。

1.store/modules/menu.js

import {reqMenuList} from "../../utils/request"
const state = {
    // 菜单的list
    list:[]
}
const mutations = {
    //修改list
    changeList(state,arr){
        state.list=arr;
    }
}
const actions = {
    //页面请求
    reqListAction(context){
        //发请求
        reqMenuList().then(res=>{
            context.commit("changeList",res.data.list)
        })
    }
}

const getters = {
    list(state){
        return state.list
    }
}

export default {
    state,
    mutations,
    actions,
    getters,
    namespaced: true

}

2.store/index.js 引入menu模块

import menu from "./modules/menu"
export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions,
    modules:{
        menu
    }
})

3.list.vue 通过mapGetters mapActions从状态层取数据和方法

computed: {
    ...mapGetters({
      list: "menu/list",
    }),
  },
  methods: {
    ...mapActions({
      reqListAction: "menu/reqListAction",
    }),
  }mounted(){
  	this.reqListAction()
  }

4.add.vue 里也要用list数据和reqListAction 方法

	computed: {
    ...mapGetters({
      list: "menu/list",
    }),
  },
  methods: {
    ...mapActions({
      reqListAction: "menu/reqListAction",
    }),
  }
<el-form-item label="上级菜单">
    <el-select v-model="form.pid" placeholder="请选择上级菜单" @change="changePid">
        <el-option label="*菜单" :value="0"></el-option>
        <el-option v-for="item in list" :key="item.id" :label="item.title" :value="item.id"></el-option>
    </el-select>
</el-form-item>

当添加完成的时候,刷新list数据

 //点击了添加按钮
    add(){
      
      reqMenuAdd(this.form).then(res=>{
        if(res.data.code==200){
          //成功
          successAlert(res.data.msg)

          //数据重置
          this.empty()

          //弹框消失
          this.cancel()

          //list数据要刷新
          this.reqListAction()
        }else{
          warningAlert(res.data.msg)
        }
      })
    }

8.删除交互

list.vue

<el-button type="danger" @click="del(scope.row.id)">删除</el-button>

//删除
    del(id) {
      this.$confirm("你确定要删除吗?", "删除提示", {
        confirmButtonText: "删除",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          //点了确定按钮
          reqMenuDel(id).then(res=>{
            if(res.data.code==200){
              successAlert(res.data.msg)
              this.reqListAction();
            }else{
              warningAlert(res.data.msg)
            }
          })
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除",
          });
        });
    },

9.编辑功能

1.list.vue 点击了编辑按钮

 <el-button type="primary" @click="edit(scope.row.id)">编辑</el-button>

//编辑
    edit(id){
      this.$emit("edit",id)
    },

2.menu.vue 触发自定事件 edit,收到id, 弹出弹框,让add.vue 的look函数执行

	<!-- 表格 -->
    <v-list @edit="edit"></v-list>

    <!-- 添加组件 -->
    <v-add :info="info" ref="add"></v-add>

//编辑
    edit(id){
      this.info.isshow=true;
      this.info.isAdd=false;
      //让add组件发起获取详情的请求
      this.$refs.add.look(id)
    }

3.add.vue look函数开始请求一条数据,请求完成后,赋值给form,但是请求得到的数据中没有id字段,但是一会儿修改是要id的,所以要补一个id

//获取菜单详情 (1条)
    look(id) {
      //发请求
      reqMenuDetail(id).then((res) => {
        if (res.data.code == 200) {
          //这个时候form是没有id的
          this.form = res.data.list;
          this.form.id = id;
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

4.add.vue 点击了修改按钮

 	//修改
    update() {
      reqMenuUpdate(this.form).then((res) => {
        if (res.data.code == 200) {
          successAlert(res.data.msg);
          this.empty();
          this.cancel();
          this.reqListAction();
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

10.弹框bug

点击了 编辑–》取消 --》添加 ,这个时候,直接有上一次的数据。要改成:

如果是添加开的弹框,就什么都不做;如果是编辑开的弹框,就清除form

<el-dialog @closed="close"></el-dialog>

	//弹框消失完成
    close(){
      //如果是添加开的弹框,就什么都不做;如果是编辑开的弹框,就清除form
      if(!this.info.isAdd){
        this.empty()
      }
    },

8.role.vue

1.拆分组件 list add 类似菜单

2.静态页实现

list.vue

和菜单list一样

add.vue
  <!-- 树形控件 -->
<!-- data要展示的数组 -->
<!-- props 配置 :children 用来判断是否有下一层的字段;label用来展示在页面中的字段 -->
<el-form-item label="角色权限">
    <el-tree
             ref="tree"
             :data="menuList"
             show-checkbox
             node-key="id"
             :props="{children: 'children',label: 'title',}"
             >
    </el-tree>
</el-form-item>

3.交互接口书写

request.js

/*********角色管理***************/
//添加
export const reqRoleAdd=(params)=>{
    return axios({
        url:baseUrl+"/api/roleadd",
        method:"post",
        data:qs.stringify(params)
    })
}

//列表
export const reqRoleList=()=>{
    return axios({
        url:baseUrl+"/api/rolelist",
        method:"get",
    })
}
//删除
export const reqRoleDel=(id)=>{
    return axios({
        url:baseUrl+"/api/roledelete",
        method:"post",
        data:qs.stringify({id:id})
    })
}

//1条
export const reqRoleDetail=(id)=>{
    return axios({
        url:baseUrl+"/api/roleinfo",
        method:"get",
        params:{
            id:id
        }
    })
}

//修改
export const reqRoleUpdate=(params)=>{
    return axios({
        url:baseUrl+"/api/roleedit",
        method:"post",
        data:qs.stringify(params)
    })
}


4.获取列表数据

1.store/modules/role.js

import {reqRoleList} from "../../utils/request"
const state = {
    // list
    list:[]
}
const mutations = {
    //修改list
    changeList(state,arr){
        state.list=arr;
    }
}
const actions = {
    //页面请求
    reqListAction(context){
        //发请求
        reqRoleList().then(res=>{
            let list=res.data.list?res.data.list:[]
            context.commit("changeList",list)
        })
    }
}

const getters = {
    list(state){
        return state.list
    }
}

export default {
    state,
    mutations,
    actions,
    getters,
    namespaced: true

}

2.store/index.js 引入

import role from "./modules/role"
export default new Vuex.Store({
   
    modules:{
        menu,
        role
    }
})

3.pages/role/list.vue 引入数据和方法

 computed: {
    ...mapGetters({
      list: "role/list",
    }),
  },
  methods: {
    ...mapActions({
      reqListAction: "role/reqListAction",
    }),
  }mounted() {
    this.reqListAction();
  },

5.添加

0.由于添加的树形控件需要菜单的list ,所以从vuex中取出

 computed: {
    ...mapGetters({
      //菜单list
      menuList: "menu/list",
    }),
  },
  methods: {
    ...mapActions({
      //请求菜单list
      reqMenuListAction: "menu/reqListAction",
      //角色的list
      reqRoleList:"role/reqListAction"
    }),
  },
   mounted() {
    //如果菜单list没有请求过,就请求一下,如果请求过了,就不用请求了
    if(this.menuList.length==0){
      this.reqMenuListAction()
    }
  },

1.初始数据form,保持和后端字段一致

 data() {
    return {
     form: {
        rolename:"",
        menus:'[]',
        status: 1,
      },
    };
  },

2.当点击了添加按钮,先获取到树形控件的数据,赋值给form,才做数据交互。

//点击了添加按钮
    add() {
      树形控件取值 this.$refs.tree.getCheckedKeys()
      this.form.menus=JSON.stringify(this.$refs.tree.getCheckedKeys())
      reqRoleAdd(this.form).then((res) => {
        if (res.data.code == 200) {
          //成功
          successAlert(res.data.msg);

          //数据重置
          this.empty();

          //弹框消失
          this.cancel();

          //list数据要刷新
          this.reqRoleList();
        } else {
          warningAlert(res.data.msg);
        }
      });
    },
    

由于form和树形控件没有关联,所以在清空的时候,清空了form,还要把属性控件清空

 	//数据重置
    empty() {
      this.form = {
        rolename:"",
        menus:'[]',
        status: 1,
      };
      //树形控件设置值
      this.$refs.tree.setCheckedKeys([])
    },

6.删除

//删除2
    dele(id) {
      //点了确定按钮
      reqRoleDel(id).then((res) => {
        if (res.data.code == 200) {
          successAlert(res.data.msg);
          this.reqListAction();
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

7.编辑

点了编辑,获取到数据,赋值给form,需要补id,而且树形控件也需要赋值

//获取菜单详情 (1条)
    look(id) {
      //发请求
      reqRoleDetail(id).then((res) => {
        if (res.data.code == 200) {
          //这个时候form是没有id的
          this.form = res.data.list;
          this.form.id=id
          //给树形控件赋值
          this.$refs.tree.setCheckedKeys(JSON.parse(this.form.menus))
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

8.修改

树形控件数据赋值给form

//修改
    update() {
      this.form.menus=JSON.stringify(this.$refs.tree.getCheckedKeys())
      reqRoleUpdate(this.form).then((res) => {
        if (res.data.code == 200) {
          successAlert(res.data.msg);
          this.empty();
          this.cancel();
          this.reqRoleList();
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

9.manage.vue 管理员管理

1.拆分组件

list add 粘贴了menu 的list 和 add

2.静态页

list.vue

add.vue

3.交互接口书写

/*********管理员管理***************/
//添加
export const reqManageAdd=(params)=>{
    return axios({
        url:baseUrl+"/api/useradd",
        method:"post",
        data:qs.stringify(params)
    })
}

//管理员总数
export const reqManageCount=()=>{
    return axios({
        url:baseUrl+"/api/usercount",
    })
}

//列表 params={page:1,size:10}
export const reqManageList=(params)=>{
    return axios({
        url:baseUrl+"/api/userlist",
        method:"get",
        params:params
    })
}
//删除
export const reqManageDel=(uid)=>{
    return axios({
        url:baseUrl+"/api/userdelete",
        method:"post",
        data:qs.stringify({uid:uid})
    })
}

//1条
export const reqManageDetail=(uid)=>{
    return axios({
        url:baseUrl+"/api/userinfo",
        method:"get",
        params:{
            uid:uid
        }
    })
}

//修改
export const reqManageUpdate=(params)=>{
    return axios({
        url:baseUrl+"/api/useredit",
        method:"post",
        data:qs.stringify(params)
    })
}

4.获取列表数据

1.store/modules/manage.js

import {reqManageList} from "../../utils/request"
const state = {
    // 菜单的list
    list:[]
}
const mutations = {
    //修改list
    changeList(state,arr){
        state.list=arr;
    }
}
const actions = {
    //页面请求
    reqListAction(context){
        //发请求
        reqManageList({page:1,size:10}).then(res=>{
            let list=res.data.list?res.data.list:[]
            context.commit("changeList",list)
        })
    }
}

const getters = {
    list(state){
        return state.list
    }
}

export default {
    state,
    mutations,
    actions,
    getters,
    namespaced: true

}

2.store/index.js 引入manage模块

import manage from "./modules/manage"
export default new Vuex.Store({
    state,
    getters,
    mutations,
    actions,
    modules:{
        menu,
        role,
        manage
    }
})

3.list.vue使用

 computed: {
    ...mapGetters({
      list: "manage/list",
    }),
  },
  methods: {
    ...mapActions({
      reqListAction: "manage/reqListAction",
    }),
  },
  mounted() {
    this.reqListAction();
  },

5.添加功能-add.vue

0.获取角色列表

 computed: {
    ...mapGetters({
      roleList: "role/list",
    }),
  },
  methods: {
    ...mapActions({
      reqRoleListAction: "role/reqListAction",
      reqManageList:"manage/reqListAction"
    }),
  },
  mounted() {
    //如果没有请求过角色管理的数据,就请求一次,如果请求过了,就不用请求了
    if(this.roleList.length==0){
      this.reqRoleListAction()
    }
  },

1.定义form初始值

data() {
    return {
      
      form: {
       roleid:"",
       username:"",
       password:"",
       status:1
      },
    };
  },

2.点击了添加按钮

//点击了添加按钮
    add() {
      reqManageAdd(this.form).then((res) => {
        if (res.data.code == 200) {
          //成功
          successAlert(res.data.msg);

          //数据重置
          this.empty();

          //弹框消失
          this.cancel();

          //list数据要刷新
          this.reqManageList();
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

3.清空form

//数据重置
    empty() {
      this.form ={
       roleid:"",
       username:"",
       password:"",
       status:1
      };
    },
    

6.删除

 <del-btn @confirm="dele(scope.row.uid)"></del-btn>

  //删除2
    dele(uid) {
      //点了确定按钮
      reqManageDel(uid).then((res) => {
        if (res.data.code == 200) {
          successAlert(res.data.msg);
          this.reqListAction();
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

7.编辑

 <el-button type="primary" @click="edit(scope.row.uid)">编辑</el-button>

add.vue

 //获取菜单详情 (1条)
    look(uid) {
      //发请求
      reqManageDetail(uid).then((res) => {
        if (res.data.code == 200) {
          //这个时候form是没有id的
          this.form = res.data.list;
           //密码是加密过的,所以需要重置密码
          this.form.password=""
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

8.修改

//修改
    update() {
      reqManageUpdate(this.form).then((res) => {
        if (res.data.code == 200) {
          successAlert(res.data.msg);
          this.empty();
          this.cancel();
          this.reqManageList();
        } else {
          warningAlert(res.data.msg);
        }
      });
    },

9.分页

1.分页组件
  <!-- 分页组件 -->
    <!-- 
    total:设置总数 
    page-size:每页数量 
    有多少页,不用自己计算 

    current-change:当前页码发生了改变,就会触发,参数就是当前的页码
    -->
    <el-pagination background layout="prev, pager, next" :total="total" :page-size="size"
    @current-change="changePage"
    >
    </el-pagination>

2.状态层(store/modules/manage.js)定义了状态size ,并导出
const state = {
   
    //一页的数量
    size: 2,
}
const getters = {
   
    size(state) {
        return state.size
    },
}

组件使用

  <el-pagination background layout="prev, pager, next"  
                 :page-size="size"
   
    >
    </el-pagination>

3.总数

定义了一个total状态,他的修改的mutations,和请求的action,导出的getters

const state = {
   
    //总数
    total:0,
}
const mutations = {
   
    //修改total 
    changeTotal(state,num){
        state.total=num;
    },

}
const actions = {
   
    //获取总数的请求
    reqTotalAction(context){
        reqManageCount().then(res=>{
            context.commit("changeTotal",res.data.list[0].total)
        })
    },
}

const getters = {
   
    total(state){
        return state.total;
    }
}

list.vue取出total使用

<el-pagination background layout="prev, pager, next"
               :total="total" 
    
    >
    </el-pagination>

4.页码

在状态层定义了一个状态页码 page,修改

const state = {
    //页码
    page:1,
}
const mutations = {
   
    //修改页码
    changePage(state,page){
        state.page=page
    }

}
const actions = {
   
    //组件修改了页码
    changePageAction(context,page){
        //修改页码
        context.commit("changePage",page)
        //发起list请求
        context.dispatch('reqListAction')
    }
}


list.vue 绑定事件,触发修改页码

<el-pagination background layout="prev, pager, next" :total="total" :page-size="size"
    @current-change="changePage"
    >
    </el-pagination>

 methods: {
    ...mapActions({
      changePageAction:"manage/changePageAction"
    }),
   
    //修改了当前页码
    changePage(e){
      this.changePageAction(e)
    }
  },

5.在请求list数据的时候,不在使用固定值,而是使用变量作为参数
const actions = {
    //页面请求
    reqListAction(context) {
        //发请求
        reqManageList(
            { page: context.state.page, size: context.state.size }
        ).then(res => {
            let list = res.data.list ? res.data.list : []
            context.commit("changeList", list)
        })
    },
}

6.注意

一进来挂载完成、添加完成、删除完成 都要重新请求总数。

7.bug

假设目前page是3,在page3 减到没有数据了,就会显示“暂无数据”,但是应该展示第2页的数据。

//页面请求
    reqListAction(context) {
        //发请求
        reqManageList({ page: context.state.page, size: context.state.size }).then(res => {
            //如果去list的时候,取到null,那么有可能是最后一页没数据了,需要减一页,再次请求list
            let list = res.data.list ? res.data.list : []

            //如果取到的数据是null,并且不是第一页,那么就页码减1,重新请求list
            if(context.state.page>1&&list.length==0){
                context.commit("changePage",context.state.page-1)
                context.dispatch("reqListAction")
                return;
            }

            
            context.commit("changeList", list)
        })
    },

10、cate商品分类

1、拆分页面(复制menu页面)
2、add.vue

 <!-- 原生 -->
 <el-form-item label="图片">
   <div class="fileBox">
     <h3>+</h3>
     <img :src="imgUrl" v-if="imgUrl">
     <input type="file" @change="changeFile" v-if="info.isShow">
   </div>
 </el-form-item>
 <!-- element-ui -->
 <el-form-item label="图片">
   <el-upload
     class="avatar-uploader"
     action="#"
     :show-file-list="false" :on-change="changeFile2">
     <img v-if="imgUrl" :src="imgUrl" class="avatar" />
     <i v-else class="el-icon-plus avatar-uploader-icon"></i>
   </el-upload>
 </el-form-item>

// 原生改变图片
changeFile(e) {
 let file = e.target.files[0];
 // 限制大小file.size      限制大小为2M     B-->1024KB   1024KB-->1M
 if(file.size > 2*1024*1024){
   warningAlert("图片大小超出限制")
   return
 }
 // URL.createObjectURL   可以将图片信息转换成具体的图片
 this.imgUrl = URL.createObjectURL(file);
 this.form.img = file;
},

// element-ui改变图片
changeFile2(e){
 let file = e.raw
 this.imgUrl = URL.createObjectURL(file);
 this.form.img = file
}

3、在添加的请求里面

export const reqCateAdd = (form) => {
  // 因为form中包含了文件,所以必须要这样传参
  let data = new FormData()
  // data.append("pid",1)
  // data.append("catename",'hahaha')
  // data.append("img",file)
  // data.append("status",1)

  for(let i in form){
    data.append(i,form[i])
  }

  return axios({
    url: baseUrl + "/api/cateadd",
    method: "post",
    data: data
  })
}

4、列表渲染图片

<el-table-column label="图片">
  <template slot-scope="scope">
    <div class="imgBox">
      <img :src="$imgUrl+scope.row.img" alt="">
    </div>
  </template>
</el-table-column>

// 开发环境
let baseUrl = "/api"
Vue.prototype.$imgUrl = "http://localhost:3000/"

// 上线环境
// let bstUrl = ""
// Vue.prototype.$imgUrl = ""

5、编辑功能也要修改request.js

export const reqCateUpdate = (form)=>{
  let data = new FormData()

  for(let i in form){
    data.append(i,form[i])
  }

  return axios({
    url:baseUrl+"/api/cateedit",
    method:"POST",
    data:data
  })
}

11、specs商品规格

1、拆分静态页(复制manage页面)
2、add.vue静态页面

 <el-form-item label="规格属性" v-for="(item,index) in attrArr" :key="index">
   <div class="inpTop">
     <el-input v-model="item.value"></el-input>
     <el-button type="primary" @click="addAttr" v-if="index==0">新增规格属性</el-button>
     <el-button type="danger" @click="delAttr(index)" v-else>删除</el-button>
   </div>
 </el-form-item>



  data() {
    return {
      attrArr:[
        {value:''},
        {value:''}
      ],
    };
  }

3、点击添加商品规格和删除

// 点击新增按钮
addAttr(){
  this.attrArr.push({value:''})
},
// 点击删除按钮
delAttr(index){
  this.attrArr.splice(index,1)
}

4、点击提交按钮

add() {
  this.form.attrs = JSON.stringify(this.attrArr.map(item=>item.value))
  reqSpecsAdd(this.form).then((res) => {
    if (res.data.code === 200) {
      // 弹窗
      successAlert(res.data.msg);
      // 取消弹窗并重置form数据
      this.cancel();
      // 刷新list列表
      this.reqManageList();
      // 重新获取总条数
      this.reqChangeTotal();
    } else {
      warningAlert(res.data.msg);
    }
  });
}

5、在状态层处理一下list数据

list.forEach(item=>{
  item.attrs = JSON.parse(item.attrs)
})

 <el-table-column prop="rolename" label="规格属性">
   <template slot-scope="scope">
     <div>
       <el-tag v-for="item in scope.row.attrs" :key="item">{{item}}</el-tag>
     </div>
   </template>
 </el-table-column>

6、删除功能

del2(id) {
  reqSpecsDel(id).then((res) => {
    if (res.data.code == 200) {
      successAlert(res.data.msg);
      // 重新请求列表
      this.reqChangeList();
      // 重新请求总条数
      this.reqChangeTotal()
    } else {
      warningAlert(res.data.msg);
    }
  });
}

7、获取一条详情信息

getOne(id) {
  reqSpecsOne(id).then((res) => {
    this.form = res.data.list[0];
    this.attrArr = JSON.parse(this.form.attrs).map(item=>({value:item}))
  });
}

8、编辑功能

update() {
  this.form.attrs = JSON.stringify(this.attrArr.map(item=>item.value))
  reqSpecsUpdate(this.form).then((res) => {
    if (res.data.code === 200) {
      successAlert(res.data.msg);
      // 关闭弹窗并重置数据
      this.cancel();
      // 刷新list列表
      this.reqManageList();
    } else {
      warningAlert(res.data.msg);
    }
  });
},
}

12、goods商品管理

1、拆分静态页
2、统一设置接口
3、add.vue
3.1、设置静态页
3.2、设置初始form

 form: {
   first_cateid: "",
   second_cateid: "",
   goodsname: "",
   price: "",
   market_price: "",
   img: "",
   description: "",
   specsid: "",
   specsattr: "",
   isnew: 1,
   ishot: 1,
   status: 1,
 }

3.3、绑定数据v-model
3.3.1、通过状态层获取一级分类
3.3.2、二级分类

// 用于渲染二级分类
secondArr: [],
// 当一级分类改变的时候
changeFirst() {
  reqCateList({ pid: this.form.first_cateid }).then((res) => {
    // 清空form里面的second_cateid
    this.form.second_cateid = "";
    // 用于渲染二级分类
    this.secondArr = res.data.list;
  });
}

3.3.3、图片

 <el-form-item label="图片">
   <div class="fileBox">
     <h3>+</h3>
     <img :src="imgUrl" v-if="imgUrl" />
     <input type="file" @change="changeFile" v-if="info.isShow" />
   </div>
 </el-form-item>


 // 改变了图片
 changeFile(e) {
   console.log(e);
   let file = e.target.files[0];
   // 限制大小file.size      限制大小为2M     B-->1024KB   1024KB-->1M
   if (file.size > 2 * 1024 * 1024) {
     warningAlert("图片大小超出限制");
     return;
   }
   // URL.createObjectURL   可以将图片信息转换成具体的图片
   this.imgUrl = URL.createObjectURL(file);
   this.form.img = file;
 }

3.3.4、商品规格
通过状态层取到specs商品规格
3.3.5、规格属性

// 用于渲染规格属性
specsArr: []


// 改变了商品规格
changeSpecs() {
  // 清空规格属性
  this.specsArr = [];
  this.form.specsattr = [];
  
  // 找到specsList里面id是我需要的那一条数据
  let obj = this.specsList.find((item) => item.id == this.form.specsid);
  // 把那一条数据的attrs赋值给我的specsArr
  this.specsArr = obj.attrs;
}

4、list.vue
4.1、设置静态页
4.2、设置状态层(复制的mange状态层)
4.3、页面一进来就请求列表数据
4.4、绑定数据
5、删除
6、获取一条信息

getOne(id) {
  reqGoodsOne(id).then((res) => {
    this.form = res.data.list;
    // 补充id,因为修改的时候需要id
    this.form.id = id
    // 1、根据一级分类的id获取二级分类的列表
    reqCateList({ pid: this.form.first_cateid }).then((res) => {
      // 用于渲染二级分类
      this.secondArr = res.data.list;
    });
    // 2、处理图片
    this.imgUrl = this.$imgUrl + this.form.img
    // 3、处理规格属性 
    this.form.specsattr = JSON.parse(this.form.specsattr)
    // 4、处理规格属性的下拉框
    let obj = this.specsList.find((item) => item.id == this.form.specsid);
    this.specsArr = obj.attrs;
  });
}

7、修改功能

update() {
  let data = {
    ...this.form,
    specsattr: JSON.stringify(this.form.specsattr),
  };

  reqGoodsUpdate(data).then((res) => {
    if (res.data.code === 200) {
      successAlert(res.data.msg);
      // 关闭弹窗并重置数据
      this.cancel();
      // 刷新list列表
      this.reqChangeList()
    } else {
      warningAlert(res.data.msg);
    }
  });
}

富文本编辑器

1、官网:http://www.wangeditor.com/
2、安装方式:

npm安装 npm i wangeditor --save

CDN链接 https://unpkg.com/wangeditor/dist/wangEditor.min.js

3、使用

 // 当打开动画完成时
 opened(){
   // 加载富文本
   this.editor = new E('#div1')
   this.editor.create()

   // 设置富文本的内容
   this.editor.txt.html(this.form.description)
 }

 // 在添加请求之前
 // 将富文本的内容加入到form中
this.form.description = this.editor.txt.html()

权限

1、登录交互

login(){
  reqLogin(this.info).then(res=>{
    console.log(res)
    if(res.data.code === 200){
      successAlert(res.data.msg)
      // 1、存res.data.list。不仅存了状态层还存了本地存储
      this.reqUserInfoAction(res.data.list)

      // 2、进入页面
      this.$router.push("/")
    }else{
      warningAlert(res.data.msg)
    }
  })
}

2、设置状态层

// mutations.js
export const state = {
  userInfo:sessionStorage.getItem("userInfo")?JSON.parse(sessionStorage.getItem("userInfo")):{}
}
export const getters = {
  userInfo(state){
    return state.userInfo
  }
}
export const mutations = {
  changeUserInfo(state,info){
    state.userInfo = info

    if(info.id){
    //在本地存储也存一份
    sessionStorage.setItem("userInfo",JSON.stringify(info))
    }else{
    //清空本地存储的userInfo
    sessionStorage.removeItem("userInfo")
    }
  }
}
// actions.js
import {reqLogin} from "../util/request"

export const actions = {
  reqUserInfoAction(context,info){
    context.commit("changeUserInfo",info)
  }
}

3、登陆拦截

// 登录拦截
router.beforeEach((to, from, next) => {
  //1、如果去的是登录页,就直接进
  if (to.path == '/login') {
    next()
    return
  }
  //2、如果去的是其他页面,验证仓库里面的token是否存在,如果存在说明登录过了。
  if (store.state.userInfo.token) {
    next();
    return
  }
  next("/login")
})

4、路由独享守卫

function changeEnter(path,next) {
  // 取出你可以去的地址
  let menus_url = store.state.userInfo.menus_url
  if (menus_url.includes(path)) {
    next()
  } else {
    next("/")
  }
}

export const indexRouters = [{
    path: "menu",
    name: "菜单管理",
    component: () => import("../page/menu/menu.vue"),
    beforeEnter: (to, from, next) => {
      changeEnter("/menu",next)
    }
  }
}]

5、动态侧边栏index.vue

 <div v-for="item in userInfo.menus" :key="item.id">
   <!-- 目录 -->
   <el-submenu :index="item.id+''" v-if="item.children">
     <template slot="title">
       <i :class="item.icon"></i>
       <span>{{item.title}}</span>
     </template>
     <el-menu-item-group>
       <el-menu-item v-for="i in item.children" :key="i.id" :index="i.url">
         <span>{{i.title}}</span>
       </el-menu-item>
     </el-menu-item-group>
   </el-submenu>
   <!-- 菜单 -->
   <el-menu-item :index="item.url" v-else>
     <span>{{item.title}}</span>
   </el-menu-item>
 </div>

注意:动态侧边栏需要一些数据,数据是从状态层拿过来的
6、退出

<el-header class="header">
  <div>
    <span>{{userInfo.username}}</span>
    <el-button @click="logOut">退出</el-button>
  </div>
</el-header>
logOut(){
  // 清空状态层的userinfo
  this.reqUserInfoAction({})
  // 跳到登录页
  this.$router.push("/login")
}