阅读 676

Vue全家桶开发的后台管理项目(后台是用node写的)

前端代码在git地址: https://github.com/178600803/-.git
如果需要后端服务请私信我,或者留言
项目总体实现的就是对数据库的增删改查,以及对前台模块的管理
截图:

修改菜单弹出页面

角色管理页面.jpg
菜单管理页面.jpg
轮播图管理主菜单.jpg
添加商品弹出窗.jpg

1.打开后端

1.umall-api 安装依赖包

cnpm i 

2.navicat for mysql 创建一个usmall数据库 ,右键--》运行sql文件--》选择 无数据.sql

3.umall-api/config/global.js

exports.dbConfig = {
    host: 'localhost', //数据库地址
    user: 'root',//数据库用户名
    password: '123',//数据库用户密码
    port: 3306,
    database: 'usmall' // 数据库名字
}

4.启动后端 ,最后启动在localhost:3000

npm start

2.前端工作

1.创建项目

vue init webpack umall_admin

2.清空工作

1.assets 删完了

2.components删完了

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

4.app.vue重置

3.配置目录

-src 
    -assets 静态资源
        -css 
        -js 
    -components 公共组件
    -filters 过滤器
    -pages 路由组件
        -menu
            menu.vue
            -components 路由组件的子组件
    -router 路由
    -store 仓库
    -util 工具类
    App.vue
    main.js

4.项目搭建

1.reset.css

1.assets/css/reset.css

2.main.js引入

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

2.公共组件

1.components/index.js

export default {
    
}

2.main.js处理

//2.处理公共组件
import Components from "./components"
for(let i in Components){
  Vue.component(i,Components[i])
}

3.过滤器

1.filters/index.js

export default {
    
}

2.main.js处理

// 3.处理过滤器
import Filters from "./filters"
for(let i in Filters){
  Vue.filter(i,Filters[i])
}

4.仓库

1.安装vuex

cnpm i vuex --save

2.配置目录

-store
    index.js  导出仓库
    actions.js 根级别下的actions
    mutations.js  根级别下的mutations state getters
    -modules 模块

3.导出仓库

import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)

import actions from "./actions"
import {state,mutations,getters} from "./mutations"

export default new Vuex.Store({
    state,
    mutations,
    getters,
    actions,
    modules:{
        
    }
})

4.main.js引入仓库

//4.处理仓库
import store from "./store"
new Vue({
  store,
})

5.数据请求

1.安装模块

cnpm i axios qs --save

2.配置代理 config/index.js

    proxyTable: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true,
        pathRewrite: {
          "^/api": "http://localhost:3000"
        }
      }
    },

3.util/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.UI

1.安装

cnpm i element-ui --save

2.main.js引入

// 6.处理element-ui 
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI)

7.弹框封装 util/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'
  });
}

5.安装依赖包

cnpm i vuex axios qs element-ui --save

6.配置1级路由

1.pages下创建了login index

2.router/index.配置规则

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

3.app.vue定义路由出口

 <router-view></router-view>

7.login.vue

画了静态页

 <div class="login">
        <div class="con">
            <h3>登录</h3>
            <el-input v-model="user.name" placeholder="输入账号" clearable></el-input>
            <el-input v-model="user.pass" placeholder="输入密码" clearable show-password></el-input>
            <div class="btn-box">
                <el-button type="primary" @click="login">登录</el-button>
            </div>
        </div>
    </div>

8.index.vue

1.粘贴布局

        <el-container class="con">
            <el-aside width="200px"></el-aside>
            <el-container>
                <el-header>Header</el-header>
                <el-main>Main</el-main>
            </el-container>
        </el-container>

2.粘贴导航

 <!-- 左侧导航 -->
                <el-menu
                    default-active="3"
                    unique-opened
                    class="el-menu-vertical-demo"
                    @open="handleOpen"
                    @close="handleClose"
                    background-color="#20222A"
                    text-color="#fff"
                    active-text-color="#ffd04b">
                     <el-menu-item index="3">
                        <i class="el-icon-menu"></i>
                        <span slot="title">首页</span>
                    </el-menu-item>
                    <el-submenu index="1">
                        <template slot="title">
                            <i class="el-icon-location"></i>
                            <span>系统设置</span>
                        </template>
                        <el-menu-item-group>
                            <el-menu-item index="1-1">菜单管理</el-menu-item>
                            <el-menu-item index="1-2">角色管理</el-menu-item>
                            <el-menu-item index="1-2">管理员管理</el-menu-item>
                        </el-menu-item-group>
                       
                    </el-submenu>
                    <el-submenu index="2">
                        <template slot="title">
                            <i class="el-icon-location"></i>
                            <span>商城管理</span>
                        </template>
                        <el-menu-item-group>
                            <el-menu-item index="1-1">商品分类</el-menu-item>
                            <el-menu-item index="1-2">商品规格</el-menu-item>
                            <el-menu-item index="1-2">商品管理</el-menu-item>
                            <el-menu-item index="1-2">会员管理</el-menu-item>
                            <el-menu-item index="1-2">轮播图管理</el-menu-item>
                            <el-menu-item index="1-2">秒杀活动</el-menu-item>
                        </el-menu-item-group>
                       
                    </el-submenu>
                    
                </el-menu>

9.二级路由规则

1.pages下创建 home menu role manage classify spec goods member banner seckill

2.路由规则

//首页下面的二级路由规则
const indexRoutes=[
  {
    path:"menu",
    component:menu
  },
  {
    path:"role",
    component:role
  },
  {
    path:"manage",
    component:manage
  },
  {
    path:"classify",
    component:classify
  },
  {
    path:"spec",
    component:spec
  },
  {
    path:"goods",
    component:goods
  },
  {
    path:"banner",
    component:banner
  },
  {
    path:"member",
    component:member
  },
  {
    path:"seckill",
    component:seckill
  },

]




    {
      path: "/",
      component: index,
      children:[
        {
          path:"",
          component:home
        },
        ...indexRoutes
      ]
    }

2.index.vue 定义二级路由出口

                <el-main>
                    <!-- 2级路由出口 -->
                    <router-view></router-view>
                </el-main>

3.将导航改用路由模式,index对应path

<el-menu
         router
         default-active="/"
         unique-opened
         class="el-menu-vertical-demo"
         @open="handleOpen"
         @close="handleClose"
         background-color="#20222A"
         text-color="#fff"
         active-text-color="#ffd04b">
    <el-menu-item index="/">
        <i class="el-icon-menu"></i>
        <span slot="title">首页</span>
    </el-menu-item>
    <el-submenu index="1">
        <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-menu>

10.菜单管理

1.拆分组件

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

        <!-- 数据列表  -->
        <v-list @emit="emit($event)"></v-list>

        <!-- 添加弹框 -->
        <v-add :info="info" @hide="hide" ref="add"></v-add>
    </div>

2.绘制添加组件

<el-dialog :title="info.title" :visible.sync="info.isShow"
            @closed="close"
        >
    <el-form :model="form">
        <el-form-item label="菜单名称" :label-width="width">
            <el-input v-model="form.title" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="上级菜单" :label-width="width">
            <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>
        <el-form-item label="菜单类型" :label-width="width">
            <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="菜单图标" :label-width="width" v-if="form.type==1">
            <el-select v-model="form.icon" placeholder="请选择目录图标">
                <el-option value="el-icon-setting">
                    <i class="el-icon-setting"></i>
                </el-option>
                <el-option value="el-icon-goods">
                    <i class="el-icon-goods"></i>
                </el-option>
                <el-option value="el-icon-user">
                    <i class="el-icon-user"></i>
                </el-option>
                <el-option value="el-icon-camera">
                    <i class="el-icon-camera"></i>
                </el-option>
            </el-select>
        </el-form-item>
        <el-form-item label="菜单地址" :label-width="width" v-else>
            <el-select v-model="form.url" >
                <el-option value="" label="--请选择--" disabled></el-option>
                <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="状态" :label-width="width">
            <el-switch v-model="form.status" :active-value="1" :inactive-value="2"></el-switch>
        </el-form-item>
    </el-form>
    <div slot="footer" class="dialog-footer">
        <el-button @click="cancel">取 消</el-button>
        <el-button type="primary" @click="add" v-if="info.isAdd">添 加</el-button>
        <el-button type="primary" @click="update" v-else>修 改</el-button>
    </div>
</el-dialog>

3.弹框状态

1.定义数据 menu.vue

 data() {
        return {
            //传递给子组件的信息
            info:{
                //添加弹框出现状态
                isShow:false,
                title:"添加菜单",
                isAdd:true
            }
        }
    },

2.info传递给add.vue

<!-- 添加弹框 -->
 <v-add :info="info"></v-add>

3.add.vue接收

props:["info"],

4.使用info

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

5.add.vue 点击了取消

 <el-button @click="cancel">取 消</el-button>
//点击了取消
        cancel(){
            this.$emit("hide")
        },

menu.vue

<v-add  @hide="hide"></v-add>

// 弹框消失
hide(){
    this.info.isShow=false;
},

4.添加操作

1.定义初始数据

 //表单数据
            form:{
                pid:0,
                title:"",
                icon:"",
                type:1,
                url:"",
                status:1
            }

2.通过v-model绑定到view上

3.点击了“添加” 按钮,准备发送添加请求

request.js

//菜单添加
export const reqAddMenu = (form) => {
  return axios({
    url: baseUrl + "/api/menuadd",
    method: "post",
    data: qs.stringify(form)
  })
}

发送添加请求

        //添加
        add(){
            reqAddMenu(this.form).then(res=>{
                if(res.data.code==200){
                    //添加成功
                    successAlert(res.data.msg)
                    //弹框消失
                    this.$emit("hide")
                    //数据重置
                    this.empty()
                    //重新获取list
                    this.reqList()
                }else{
                    warningAlert(res.data.msg)
                }
            })
        },
        //重置form数据
        empty(){
            this.form={
                pid:0,
                title:"",
                icon:"",
                type:1,
                url:"",
                status:1
            }
        },

5.列表获取

由于很多组件都会使用到列表数据,所以决定将列表数据写在vuex

1.store/modules/menu.js

import {
  reqMenuList
} from "@/util/request.js"
const state = {
  //菜单列表
  list: []
}

const mutations = {
  //修改list
  changeList(state, arr) {
    state.list = arr;
  }
}

const actions = {
  //请求
    reqListAction(context){
        reqMenuList({istree:true}).then(res=>{
            context.commit("changeList",res.data.list)
        })
    }
}
const getters = {
    list(state){
        return state.list
    }
}
export default {
  state,
  mutations,
  actions,
  getters,
  namespaced: true
}

request.js

//菜单列表
export const reqMenuList = (params) => {
  return axios({
    url: baseUrl + "/api/menulist",
    method: "get",
    params: params
  })
}

2.在store中添加menu模块

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

3.menu/components/list.vue

import {mapGetters,mapActions} from "vuex"
export default {
    computed:{
       ...mapGetters({
            list:"menu/list"
       })
    },
   
    methods: {
        ...mapActions({
            reqList:"menu/reqListAction"
        })
    },
    mounted() {
        //一进来 走请求
        this.reqList()
    }
}

6.编辑

1.list.vue触发了编辑按钮

 <el-button type="primary" @click="edit(scope.row.id)">编辑</el-button>
     //点击了编辑按钮,通知menu,点了编辑
        edit(id){
            this.$emit("emit",id)
        }

2.menu.vue接收事件

 <v-list @emit="emit($event)"></v-list>
<v-add ref="add"></v-add>
 //触发了编辑
        emit(id){
           this.info={
                isShow:true,
                title:"修改菜单",
                isAdd:false
            }
            //父组件调用子组件的方法
            this.$refs.add.look(id)
        }

3.add.vue look执行,查询某一条数据

request.js

//菜单详情
export const reqMenuDetail = (params) => {
  return axios({
    url: baseUrl + "/api/menuinfo",
    method: "get",
    params: params
  })
}

add.vue

        //查看一条数据
        look(id){

            reqMenuDetail({id:id}).then(res=>{
                this.form=res.data.list
                this.form.id=id;
            })
        },

4.点击了修改按钮

request.js


//菜单修改
export const reqMenuUpdate = (form) => {
  return axios({
    url: baseUrl + "/api/menuedit",
    method: "post",
    data: qs.stringify(form)
  })
}

add.vue

        //点击了修改
        update(){
            reqMenuUpdate(this.form).then(res=>{
                if(res.data.code==200){
                    successAlert("更新成功")
                    this.$emit("hide")
                    this.empty()
                    this.reqList()
                }else{
                    warningAlert(res.data.msg)
                }
            })
        }

7.做完编辑,点击编辑--》取消--》添加,数据存在 bug

<el-dialog  @closed="close"></el-dialog>
//弹框关闭完成
close(){
    // 如果是编辑,取消了,就要清空
    if(!this.info.isAdd){
        this.empty()
    }
},

8.删除

1.list.vue 点击了删除按钮

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

2.request.js

//菜单删除 params={id:1}
export const reqMenuDel = (params) => {
  return axios({
    url: baseUrl + "/api/menudelete",
    method: "post",
    data:qs.stringify(params)
  })
}

3.二次确认是否删除

//删除
del(id){
    this.$confirm('你确定要删除吗?', '提示', {
        confirmButtonText: '删除',
        cancelButtonText: '取消',
        type: 'warning'
    }).then(() => {

        //点击了确定,发起删除请求
        reqMenuDel({id:id}).then(res=>{
            if(res.data.code==200){
                successAlert("删除成功")
                this.reqList()
            }else{
                warningAlert(res.data.msg)
            }
        })

    }).catch(() => {
        this.$message({
            type: 'info',
            message: '已取消删除'
        });          
    });
}

11.角色管理

1.拆分组件 role.vue

    <div>
        <!-- 添加按钮 -->
        <el-button type="primary">添加</el-button>

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

        <!-- 添加弹框 -->
        <v-add></v-add>
    </div>

2.绘制list.vue

        <el-table
            :data="list"
            style="width: 90%;margin:10px auto;margin-bottom: 20px;"
            row-key="id"
            border
            >
            <el-table-column
                prop="id"
                label="角色编号"
                sortable
                width="180">
            </el-table-column>
            <el-table-column
                prop="title"
                label="角色名称"
                sortable
                width="180">
            </el-table-column>
           
            <el-table-column
                
                label="状态">
                <template slot-scope="scope">
                    <el-button type="primary" v-if="scope.row.status==1">启用</el-button>
                    <el-button type="info" v-else>禁用</el-button>
                </template>
            </el-table-column>
            <el-table-column
               
                label="操作">
                <template slot-scope="scope">
                    <el-button type="primary" @click="edit(scope.row.id)">编辑</el-button>
                    <el-button type="danger" @click="del(scope.row.id)">删除</el-button>
                </template>
            </el-table-column>
        </el-table>

3.add.vue 弹框的出现和消失

1.role.vue 定义控制弹框的变量 info

 data() {
        return {
            //控制add的状态
            info:{
                isShow:false,
                title:"添加角色",
                isAdd:true
            }
        }
    },

2.role.vue 点击了添加按钮

methods: {
        //点击了添加按钮
        willadd(){
            this.info={
                isShow:true,
                title:"添加角色",
                isAdd:true
            }
        }
    },

3.role.vue 将info传递给add.vue

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

4.add.vue接收

props:["info"]

5.点击了取消按钮

        //点击了取消
        cancel(){
            this.info.isShow=false;
        }

4.角色添加

1.画了静态页

 <el-tree
          :data="menuList"
          show-checkbox
          node-key="id"
          ref="tree"
          :props="{children:'children',label:'title'}">
</el-tree>

2.request.js

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

//角色列表
export const reqRoleList = () => {
  return axios({
    url: baseUrl + "/api/rolelist",
    method: "get",
  })
}

//角色详情
export const reqRoleDetail = (params) => {
  return axios({
    url: baseUrl + "/api/roleinfo",
    method: "get",
    params: params
  })
}

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

//角色删除 params={id:1}
export const reqRoleDel = (params) => {
  return axios({
    url: baseUrl + "/api/roledelete",
    method: "post",
    data:qs.stringify(params)
  })
}

3.从vuex中取出menu的list,以及修改menuList的动作 ,在tree上展示

import {mapGetters,mapActions} from "vuex"
export default {
    computed:{
        ...mapGetters({
            "menuList":"menu/list"
        })
    },
     methods: {
        ...mapActions({
            "reqMenuList":"menu/reqListAction"
        }),
     },
     mounted() {
        //如果menuList数组是个空的,要发起请求得到
        if(this.menuList.length==0){
            this.reqMenuList()
        }
    }
}

4.点击了添加按钮

        //数据重置
        empty(){
            this.form={
                rolename:"",
                menus:[],
                status:1
            }
            //重置树形控件
            this.$refs.tree.setCheckedKeys([])
        },
        //点击了添加按钮
        add(){
            // this.$refs.tree.getCheckedKeys() 获取树形控件上的选中的key
            this.form.menus=JSON.stringify(this.$refs.tree.getCheckedKeys())
            
            reqRoleAdd(this.form).then(res=>{
                if(res.data.code==200){
                    successAlert("添加成功");
                    
                    //弹框消失
                    this.cancel();
                    //数据重置
                    this.empty()
                    
                    //刷新角色列表的数据

                }else{
                    warningAlert(res.data.msg)
                }
            })
        }

5.列表

1.列表决定放到vuex中,store/modules/role.js

import {
  reqRoleList
} from "@/util/request.js"
const state = {
  //角色列表
  list: []
}

const mutations = {
  //修改list
  changeList(state, arr) {
    state.list = arr;
  }
}

const actions = {
  //请求
  reqListAction(context) {
    reqRoleList().then(res => {
       let arr= res.data.list? res.data.list:[]
      context.commit("changeList", res.data.list)
    })
  }
}
const getters = {
  list(state) {
    return state.list
  }
}
export default {
  state,
  mutations,
  actions,
  getters,
  namespaced: true
}

2.role 放到store

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

3.list.vue中取数据和方法

import {mapGetters,mapActions} from "vuex"
export default {
    computed:{
        ...mapGetters({
            list:"role/list"
        })
    },
    methods: {
        ...mapActions({
            "reqList":"role/reqListAction"
        })
    },
    mounted() {
        //一进来就请求了角色列表数据
        this.reqList()
    }
}

4.添加成功要重新请求列表数据

        ...mapActions({
            "reqMenuList":"menu/reqListAction",
            "reqRoleList":"role/reqListAction"
        }),
            reqRoleAdd(this.form).then(res=>{
                if(res.data.code==200){
                    successAlert("添加成功");
                    //弹框消失
                    this.cancel();
                    //数据重置
                    this.empty()
                    //刷新角色列表的数据
                    this.reqRoleList()
                }else{
                    warningAlert(res.data.msg)
                }
            })

6.编辑

1.list.vue 点击了编辑,通知role.vue点了编辑

 <el-button type="primary" @click="edit(scope.row.id)">编辑</el-button>
 //点击了编辑
 edit(id){
    this.$emit("edit",id)
 }

2.role.vue收到自定义事件,弹框出现,add发送获取详情的请求

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

<!-- 添加弹框 -->
<v-add :info="info" ref="add"></v-add>
        //编辑
        edit(id){
             this.info={
                isShow:true,
                title:"修改角色",
                isAdd:false
            }
            this.$refs.add.look(id)
        }

3.add.vue查询某一条数据,id给form,树形控件单独处理

    //查看详情
        look(id){
            reqRoleDetail({id:id}).then(res=>{
                this.form=res.data.list;
                this.form.id=id;
                this.$refs.tree.setCheckedKeys(JSON.parse(res.data.list.menus))
            })
        },

4.点了修改按钮

        //修改
        update(){
            // this.$refs.tree.getCheckedKeys() 获取树形控件上的选中的key
            this.form.menus=JSON.stringify(this.$refs.tree.getCheckedKeys())
            reqRoleUpdate(this.form).then(res=>{
                if(res.data.code==200){
                    successAlert("修改成功");
                    //弹框消失
                    this.cancel();
                    //数据重置
                    this.empty()
                    //刷新角色列表的数据
                    this.reqRoleList()
                }else{
                    warningAlert(res.data.msg)
                }
            })
        }

7.删除

1.因为每次都要二次确认,所以封装了一个全局的删除组件 components/vDel.vue

<template>
    <el-button type="danger" @click="del">删除</el-button>
</template>
<script>
export default {

    methods:{
        del(){
            this.$confirm('你确定要删除吗?', '提示', {
                confirmButtonText: '删除',
                cancelButtonText: '取消',
                type: 'warning'
            }).then(() => {
               this.$emit("confirm")

            }).catch(() => {
                this.$message({
                    type: 'info',
                    message: '已取消删除'
                });          
            });
        }
    },
}
</script>

2.components/index.js 引入vDel

import vDel from "./vDel"
export default {
    vDel
}

3.list.vue调用 v-del

 <v-del @confirm="del(scope.row.id)"></v-del>
//删除
del(id){
    //点击了确定,发起删除请求
    reqRoleDel({id:id}).then(res=>{
        if(res.data.code==200){
            successAlert("删除成功")
            this.reqList()
        }else{
            warningAlert(res.data.msg)
        }
    })
}

12.管理员管理

1.添加、列表({page:1.size:20})、编辑、修改、删除 如上。

2.分页组件

total是总的数量,

page-size是一页的数量,

分页组件会自动计算有几页。 eg:total=10,page-size=2,那么就有5页

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

3.取管理员的总数量

1.request.js

export const reqUserNum=()=>{
  return axios({
    url:baseUrl+"/api/usercount",
    method:"get"
  })
}

2.store/modules/manage.js

定义了一个状态 total,用来保存管理员的总数

const state = {
  //管理员总数
  total: 0,
}

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

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

3.list.vue 取出total,并且要触发获取总数的请求

    computed:{
        ...mapGetters({
           
            total:"manage/total",
        })
    },
    methods: {
        ...mapActions({
            reqTotal:"manage/reqListNum",
        }),
      }
     mounted(){
        this.reqList()
         //一进来页面请求数据的总数
        this.reqTotal()
     }
        <el-pagination
            :total="total"
            >
        </el-pagination>

4.设置一下当前一页的数量

vuex size

const state = {
 
  //一页的条数
  size: 2,
}

const mutations = {
  
}

const actions = {
  //请求
  reqListAction(context) {
    reqUserList({
      page: 1,
      size: context.state.size
    }).then(res => {
      let arr = res.data.list ? res.data.list : []
      context.commit("changeList", arr)
    })
  },

}
const getters = {
  size(state) {
    return state.size
  },
}

list.vue

    computed:{
        ...mapGetters({
           
            size:"manage/size",
        })
    },
        <el-pagination
            :page-size="size"
            >
        </el-pagination>

5.页码发生改变,需要重新请求数据,重新请求数据需要知道是第几页。

1.在vuex中定义了一个变量page,用来记录当前是第几页

const state = {
  //当前访问的页码
  page: 1,
}

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

const actions = {
  //请求
  reqListAction(context) {
    reqUserList({
      page: context.state.page,
      size: context.state.size
    }).then(res => {
      let arr = res.data.list ? res.data.list : []
      context.commit("changeList", arr)
    })
  },
  
  //修改了page
  changePageAction(context, page) {
    context.commit("changePage", page)
    //重新请求列表数据
    context.dispatch("reqListAction")
  }
}
const getters = {
  
  page(state) {
    return state.page
  },
}

2.list.vue 点击了页码、上一页、下一页,修改页面

    computed:{
        ...mapGetters({
           
            page:"manage/page",
        })
    },
    methods: {
        ...mapActions({
            changePageAction:"manage/changePageAction"
        }),
      }

在分页组件上触发修改页码

    <el-pagination
            :page-size="size"
            layout="prev, pager, next"
            :total="total"
            :current-page='page'
            @current-change="changeCurrentPage"
            >
        </el-pagination>
        //修改了当前的页码
        changeCurrentPage(p){
            //修改一下vuex里面的page
            this.changePageAction(p)
        }

6.add.vue添加成功 总数会发生改变,重新请求总数

        add(){
            reqUserAdd(this.form).then(res=>{
                if(res.data.code==200){
                    successAlert("添加成功");
                    //弹框消失
                    this.cancel();
                    //数据重置
                    this.empty()
                    //刷新角色列表的数据
                    this.reqUserList()
                    
                    //重新请求总数
                    this.reqTotal()
                }else{
                    warningAlert(res.data.msg)
                }
            })
        },

7.list.vue删除成功 ,总数也会发生改变,而且页码也有可能变,而且最后一页有可能都已经没有了,所以页码重置

 del(id){
            //点击了确定,发起删除请求
            reqUserDel({uid:id}).then(res=>{
                if(res.data.code==200){
                    successAlert("删除成功")
                    
                     //删除完成,重新取总数,page设置为1
                    this.reqTotal()
                    this.changePageAction(1)
                }else{
                    warningAlert(res.data.msg)
                }
            })
        },

13.商品分类之 上传文件

1.原生上传文件

1.绘制html

 <!-- 原生的上传文件 -->
<div class="upload-box">
    <h3 class="upload-add">+</h3>
    <img class="upload-img" v-if="imgUrl" :src="imgUrl" alt="">
    <input class="upload-file" @change="selectImg" type="file">
</div>
.upload-box{
    width: 150px;
    height: 150px;
    border: 1px dashed #cccccc;
    position: relative;
}
.upload-add{
    width: 150px;
    height: 150px;
    text-align: center;
    line-height: 150px;
    font-size: 40px;
    color: #666;
    font-weight: 400;
}
.upload-img{
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
}
.upload-file{
    width: 150px;
    height: 150px;
    position: absolute;
    left: 0;
    top: 0;
    opacity: 0;
}

2.在input上绑定一个事件 change,可以通过事件对象获取到文件,通过URL对象将file生成一个地址,图片展示

<input class="upload-file" @change="selectImg" type="file">
 //上传文件开始
selectImg(e){

    var file=e.target.files[0];
    this.imgUrl=URL.createObjectURL(file) ;//imgUrl要在data中声明

},

    //上传文件结束
 <img class="upload-img" v-if="imgUrl" :src="imgUrl" alt="">

3判断了文件的大小和格式是否正确

//上传文件开始
selectImg(e){

    var file=e.target.files[0];

    //2M
    if(file.size>2*1024*1024){
        warningAlert("文件格式不能超过2M")
        return;
    }

    //图片类型
    let imgTypeArr=[".jpg",".jpeg",".png",".gif"]
    //获取到后缀名
    var extname=file.name.slice(file.name.lastIndexOf("."));
    //判断文件格式是否正确
    if(!imgTypeArr.includes(extname)){
        warningAlert("请上传正确的图片格式")
        return;
    }

    this.imgUrl=URL.createObjectURL(file)

},


 //上传文件结束

4.写了数据请求,因为参数中带有文件,所以需要使用formData

//分类添加
export const reqCateAdd = (form) => {
  //{z:1,a:2,v:4,d:file}
  var data=new FormData()
  for(let i in form){
    data.append(i,form[i])
  }
  return axios({
    url: baseUrl + "/api/cateadd",
    method: "post",
    data: data
  })
}

5.在add.vue

data() {
        return {
            imgUrl:"",
            //label占的宽度
            width:"100px",
            //表单数据
            form:{
                pid:0,
                catename:"",
                img:null,
                status:1
            }
        }
    },

在选择了图片之后,需要将文件保存在form.img

selectImg(e){
    
        //判断大小 判断文件格式
        this.imgUrl=URL.createObjectURL(file)
        this.form.img=file;
}

6.在list.vue取出来数据要展示图片,在原型上加$preImg

Vue.prototype.$preImg="http://localhost:3000"

7.展示图片 list.vue

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

8.编辑 取出来详情 add.vue

//查看一条数据
look(id){
    reqCateDetail({id:id}).then(res=>{
        this.form=res.data.list;
        this.form.id=id
        this.imgUrl=this.$preImg+ res.data.list.img
    })
},

9.清空 add.vue

//重置form数据
empty(){
    this.form={
        pid:0,
        catename:"",
        img:null,
        status:1
    }
    this.imgUrl=""
},

2.element-ui 上传文件

1.粘贴 el-upload,绑定了on-change属性

<el-upload
           class="avatar-uploader"
           action="#"
           :show-file-list="false"
           :on-change="changeImg"
           >
    <img v-if="imgUrl" :src="imgUrl" class="avatar">
    <i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>

2.处理changeImg

 //element-ui 上传文件
changeImg(e){
    var file=e.raw;
    this.imgUrl=URL.createObjectURL(file)
    this.form.img=file;
},

3.如果在UI框架中想修改样式,不起作用

1.important

.upload-add{
    width: 150px !important;
    height: 150px;
    text-align: center;
    line-height: 150px;
    font-size: 40px;
    color: #666;
    font-weight: 400;
}

2.如果1的方法也不行,那么就使用stylus穿透

cnpm i stylus stylus-loader --save
<style lang="stylus" scoped>
</style>

在页面找一个一定存在的标签

    .add >>> .el-upload{
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }

14.首页-图表

1.安装

cnpm i echarts --save

2.引入 home.vue

import echarts from "echarts"

3.从vuex中取出数据

export default {
    computed:{
        ...mapGetters({
            list:"cate/list"
       })
    },
    methods: {
        ...mapActions({
            reqList:"cate/reqListAction"
        }),
    },
    mounted() {
        this.reqList();
    },
}

4.数据变了,绘制图标

watch:{
        list:{
            handler(){
                if(this.list.length>0){
                    // 基于准备好的dom,初始化echarts实例
                    var myChart = echarts.init(document.getElementById('main'));

                    // 指定图表的配置项和数据
                    var option = {
                        title: {
                            text: '商品分类概况'
                        },
                        tooltip: {},
                        legend: {
                            data:['分类数量']
                        },
                        xAxis: {
                            data: this.list.map(item=>item.catename)
                        },
                        yAxis: {},
                        series: [{
                            name: '分数数量',
                            type: 'line',
                            data: this.list.map(item=>item.children?item.children.length:0)
                        }]
                    };

                    // 使用刚指定的配置项和数据显示图表。
                    myChart.setOption(option);
                }
            },
            deep:true
        }
    }

15.商品管理- 二级联动 富文本

1.二级联动

1.粘贴list.vue add.vue

2.绘制add.vue静态页面

3.写了商品的请求 6个 ,添加 删除 详情 修改 总数 列表

4.添加 初始化数据

 return {
            //二级分类的列表
            secondCateList:[],
            //规格属性的列表
            attrList:[],
            //图片临时地址
            imgUrl:"",
            //label占的宽度
            width:"100px",
            //表单数据
            form:{
                first_cateid:"",
                second_cateid:"",
                goodsname:"",
                price:"",
                market_price:"",
                img:null,
                description:"",
                specsid:"",
                specsattr:[],
                isnew:1,
                ishot:1,
                status:1
            }
        }

5.一级分类数据从vuex中取出,如果之前没有取过,就发一次请求

    computed: {
        ...mapGetters({
            //分类列表
            cateList:"cate/list",
        })
    },
    methods:{
        ...mapActions({
            //获取分类列表
            reqCateList:"cate/reqListAction",
        }),
    }
    mounted(){
        //如果没有分类就请求
        if(this.cateList.length==0){
            this.reqCateList()
        }

    }

6.修改了一级分类,需要根据一级分类计算出二级分类的列表数组

<el-form-item label="一级分类" :label-width="width">
    <el-select v-model="form.first_cateid" placeholder="请选择活动区域" @change="changeFirstId">
        <el-option label="--请选择--" value="" disabled></el-option>
        <!-- 少一个动态的数据 -->
        <el-option v-for="item in cateList" :key="item.id" :label="item.catename" :value="item.id"></el-option>
    </el-select>
</el-form-item>
<el-form-item label="二级分类" :label-width="width">
    <el-select v-model="form.second_cateid" placeholder="请选择活动区域" >
        <el-option label="--请选择--" value="" disabled></el-option>
        <!-- 少一个动态的数据 -->
        <el-option v-for="item in secondCateList" :key="item.id" :label="item.catename" :value="item.id"></el-option>
    </el-select>
</el-form-item>
//修改了一级分类
changeFirstId(){
    //找到当前first_cateid对应的那条数据的children,赋值给secondCateList,遍历
    this.secondCateList=this.cateList.find(item=>item.id==this.form.first_cateid).children
    //因为更换了一级分类,二级分类重置
    this.form.second_cateid="";
},

7.商品规格也是从vuex中取出

    computed: {
        ...mapGetters({
           //规格列表
            specList:"spec/list",
        })
    },
    methods:{
        ...mapActions({
           //获取规格列表
            reqSpecList:"spec/reqListAction",
        }),
    }
    mounted(){
        //请求全部的规格
        this.reqSpecList(true)

    }

由于之前在商品规格中,使用了分页,但是在商品添加时要的是全部的规格,不能分页,所以在请求的时候,参数变成变量,根据要不要做分页,通过一个变量来决定

const actions = {
  //请求
  reqListAction(context,bool) {
    //传递一个bool,如果是true,那么就请求全部的规格,如果是false,就请求分页
    let params=bool?{}:{
      page: context.state.page,
      size: context.state.size
    }

    reqspecsList(params).then(res => {
      let arr = res.data.list ? res.data.list : []
      arr.forEach(item=>{
        item.attrs=JSON.parse(item.attrs)
      })
      context.commit("changeList", arr)
    })
  },
 }

8.修改了商品规格,需要计算一下商品属性的数组

 <el-form-item label="商品规格" :label-width="width">
     <el-select v-model="form.specsid" placeholder="请选择活动区域"  @change="changeSpecId">
         <el-option label="--请选择--" value="" disabled></el-option>
         <!-- 少一个动态的数据 -->
         <el-option v-for="item in specList" :key="item.id" :label="item.specsname" :value="item.id"></el-option>
     </el-select>
</el-form-item>
<el-form-item label="商品属性" :label-width="width">
    <el-select v-model="form.specsattr" placeholder="请选择活动区域" multiple>
        <el-option label="--请选择--" value="" disabled></el-option>
        <!-- 少一个动态的数据 -->
        <el-option v-for="item in attrList" :key="item" :label="item" :value="item"></el-option>
    </el-select>
</el-form-item>
//修改了商品规格
changeSpecId(){
    //当商品规格变了,就计算一下商品属性要展示的数组列表
    this.attrList=this.specList.find(item=>item.id==this.form.specsid).attrs;

    //选中的商品属性重置
    this.form.specsattr=[]
},

9.点击了编辑按钮,查看某一条数据,这个时候二级分类会出现没有下拉框选项,需要手动触发

//查看一条数据
look(id){

    reqgoodsDetail({id:id}).then(res=>{
        this.form=res.data.list;
        this.form.id=id
        this.imgUrl=this.$preImg+ res.data.list.img
        this.form.specsattr=this.form.specsattr.split(",")
        //计算二级分类的列表
        this.secondCateList=this.cateList.find(item=>item.id==this.form.first_cateid).children
        //计算商品属性列表
        this.attrList=this.specList.find(item=>item.id==this.form.specsid).attrs;
    })
},

2.富文本

1.官网:http://www.wangeditor.com/

2.安装

cnpm i wangeditor --save

3.引入

import E from "wangeditor"

<el-dialog @opened="createEditor">
    <div id="editor">
        
    </div>
</el-dialog>
 //创建编辑器
createEditor(){
    //创建编辑器
    let editor=new E("#editor");
    editor.create()
},

4.取值

editor.txt.html()

5.设值

editor.txt.html('<p>123</p>')

6.使用

1.引入

import E from "wangeditor"

2.创建一个富文本编辑器

<el-dialog @opened="createEditor">
    <div id="editor">
        
    </div>
</el-dialog>
 //创建编辑器
createEditor(){
    //创建编辑器
    this.editor=new E("#editor");
    this.editor.create()
},

3.这时候出现一个bug,取消,再添加,就会多一个富文本编辑器

<div id="editor" v-if="info.isShow"></div>

4.添加或者修改之前,应该先将富文本编辑器内容取出,赋值给form.description

        //添加
        add(){
            //取出富文本编辑器的内容,赋值给form的description
            this.form.description=this.editor.txt.html()
           
            reqgoodsAdd(this.form).then(res=>{})
        },

5.当查看某一条数据的时候,数据赋值给了form,需要将form.description赋值给编辑器,如果在请求完成的时候赋值,这个时候编辑器还没有,所以赋不上,需要等编辑器创建好了再赋值

//创建编辑器
createEditor(){
    //创建编辑器
    this.editor=new E("#editor");
    this.editor.create()
    //给富文本编辑器赋值
    this.editor.txt.html(this.form.description)
},

16.登录

1.发起登录请求 login.vue

        login(){
            //登录请求
            reqUserLogin(this.user).then(res=>{
                if(res.data.code==200){//登录成功
                    //弹成功
                    successAlert("登录成功");
                    
                    //将用户信息保存 vuex
                    this.changeInfoAction(res.data.list)

                    //跳转
                    this.$router.push("/")
                }else{
                    warningAlert(res.data.msg)
                }
            })

        }

2.将用户信息存入vuex

1.store/modules/user.js

const state = {
  //用户信息
  info: sessionStorage.getItem("info") ? JSON.parse(sessionStorage.getItem("info")) : {}
}
const mutations = {
  //修改用户信息
  changeInfo(state, info) {
    state.info = info;

    if (info.username) {
      //数据同步到本地存储中
      sessionStorage.setItem("info", JSON.stringify(info))
    } else {
      sessionStorage.removeItem("info")
    }

  }
}
const actions = {
  //页面触发的修改用户信息
  changeInfoAction({
    commit
  }, info) {
    commit("changeInfo", info)
  }
}
const getters = {
  //导出
  info(state) {
    return state.info
  }
}
export default {
  state,
  mutations,
  actions,
  getters,
  namespaced: true
}

2.store/index.js引入

3.登录成功的时候调用修改info的action

 if(res.data.code==200){//登录成功
     //弹成功
     successAlert("登录成功");

     //将用户信息保存 vuex
     this.changeInfoAction(res.data.list)

     //跳转
     this.$router.push("/")
 }

4.首页的头部取出数据展示 index.vue

{{info.username}}
    computed:{
        ...mapGetters({
            info:"user/info"
        })
    },

3.数据刷新就没有了,所以希望info存入的时候,本地存储也存一份

const mutations = {
  //修改用户信息
  changeInfo(state, info) {
    state.info = info;

    if (info.username) {
      //数据同步到本地存储中
      sessionStorage.setItem("info", JSON.stringify(info))
    } else {
      sessionStorage.removeItem("info")
    }

  }
}

如果用户刷新,就看一下本地存储有没有,有就将本地存储的info取出赋值给vuex,如果没有,就赋值一个{}

const state = {
  //用户信息
  info: sessionStorage.getItem("info") ? JSON.parse(sessionStorage.getItem("info")) : {}
}

4.退出登录

logout(){
    this.changeInfoAction({})
    this.$router.push("/login")
}

5.登录拦截

除了登录路由,其他的路由,都需要登录之后才能访问 router/index.js

//登录拦截
router.beforeEach((to,from,next)=>{
  console.log(store);
  //如果去登录 next
  if(to.path==="/login"){
    next()
    return;
  }
  //去的不是登录,判断是否登录,如果登录过了,就next
  if(store.state.user.info.id){
    next()
    return
  }

  //去的不是登录,也没有登录过
  next("/login")
  

})

6.侧边栏遍历数据 index.vue

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

     </el-submenu>

     <!-- 这个直接是菜单 -->
     <el-menu-item v-else :index="item.url">{{item.title}}</el-menu-item>
</div>

7.路由独享守卫 router/index.js

//路由独享守卫判断
function beforeEnter(url, next) {
  store.state.user.info.menus_url.some(item => item == url) ? next() : next("/")
}
//首页下面的二级路由规则
export const indexRoutes = [{
    path: "menu",
    component: menu,
    name: "菜单管理",
    beforeEnter(to, from, next) {
      beforeEnter("/menu", next)
    }
  },
  {
    path: "role",
    component: role,
    name: "角色管理",
    beforeEnter(to, from, next) {
      beforeEnter("/role", next)
    }
  },
  {
    path: "manage",
    component: manage,
    name: "管理员管理",
    beforeEnter(to, from, next) {
      beforeEnter("/manage", next)
    }
  },
  {
    path: "classify",
    component: classify,
    name: "商品分类",
    beforeEnter(to, from, next) {
      beforeEnter("/classify", next)
    }
  },
  {
    path: "spec",
    component: spec,
    name: "商品规格",
    beforeEnter(to, from, next) {
      beforeEnter("/spec", next)
    }
  },
  {
    path: "goods",
    component: goods,
    name: "商品管理",
    beforeEnter(to, from, next) {
      beforeEnter("/goods", next)
    }
  },
  {
    path: "banner",
    component: banner,
    name: "轮播图管理",
    beforeEnter(to, from, next) {
      beforeEnter("/banner", next)
    }
  },
  {
    path: "member",
    component: member,
    name: "会员管理",
    beforeEnter(to, from, next) {
      beforeEnter("/member", next)
    }
  },
  {
    path: "seckill",
    component: seckill,
    name: "秒杀活动",
    beforeEnter(to, from, next) {
      beforeEnter("/seckill", next)
    }
  },

]

8.修改 umall_api 里面的app.js 下面这段后端路由拦截解开

app.use(async (req, res, next) => {
    if (!req.headers.authorization) {
        res.send(MError("请设置请求头,并携带验证字符串"));
    } else {
        if (!await checkToken(req)) { // 过期  
            res.send(Guest([],"登录已过期或访问权限受限"));
        } else {
            next();
        }
    }
});

9.请求拦截

每次发起请求,除了登录接口之外,其他接口都需要携带一个token

//请求拦截
axios.interceptors.request.use(config=>{
  //登录
  if(config.url==baseUrl+"/api/userlogin"){
    return config;
  }

  config.headers.authorization=store.state.user.info.token;
  return config;
})

10.响应拦截

每次请求回来的数据,都有可能已经显示token过期,所以如果过期了,就应该 退出登录,跳到登录页面

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

  if(res.data.msg==="登录已过期或访问权限受限"){
    warningAlert("登录已过期或访问权限受限")
    //清空info
    store.dispatch("user/changeInfoAction",{})
    //跳转到登录 
    router.push("/login")
  }
  return res;
})

作者:新时代民工

原文链接:https://www.jianshu.com/p/4f820a329d51

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐