vue项目开发



前言

前后端分离项目


一、前端

1、登录功能

1.新建登录页面和首页,并配置路由

  {
    path: '/',
    name: 'Index',
    component: Index
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue')
  }

2.登录页面代码编写
样式属性

加载静态图片,require()
背景色 FAFAFA
垂直居中
display:flex;
align-items:center;
文字居中
text-align:center;
:xl=6 :lg=7 左右拖动,文字不会换行 <el-col>标签属性
<template>
    <el-row type="flex" justify="center">
        <el-col :xl=6 :lg=7>
            <div>
                <p>欢迎来到VueAdmin管理系统</p>
                <el-image :src="require('@/assets/icon.png')" style="width: 185px;height: 185px"></el-image>
                <p>公众号 MarkerHub</p>
                <p>扫码二维码,回复【 VueAdmin 】获取登录密码</p>
            </div>
        </el-col>
        <el-col :span="1">
            <el-divider direction="vertical"></el-divider>
        </el-col>
        <el-col :xl=6 :lg=7>
            <el-form :model="loginForm" :rules="rules" ref="loginForm" label-width="100px" class="demo-loginForm">
                <el-form-item label="用户名" prop="username" style="width: 380px;">
                    <el-input v-model="loginForm.username"></el-input>
                </el-form-item>
                <el-form-item label="密码" prop="password" style="width: 380px;">
                    <el-input v-model="loginForm.password"></el-input>
                </el-form-item>
                <el-form-item label="验证码" prop="code" style="width: 380px;">
                    <el-input v-model="loginForm.code" style="width: 172px;float: left"></el-input>
                    <!--                    <el-image src="" style="float: left;margin-left: 10px;border-radius: 5px;width: auto"></el-image>-->
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="submitForm('loginForm')">登录</el-button>
                    <el-button @click="resetForm('loginForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </el-col>
    </el-row>
</template>

<script>
    export default {
        name: "Login",
        data() {
            return {
                loginForm: {
                    username: '',
                    password: '',
                    code: ''
                },
                rules: {
                    username: [
                        {required: true, message: '请输入用户名', trigger: 'blur'}
                    ],
                    password: [
                        {required: true, message: '请输入密码', trigger: 'blur'}
                    ],
                    code: [
                        {required: true, message: '请输入验证码', trigger: 'blur'},
                        {min: 5, max: 5, message: '长度为5个字符', trigger: 'blur'}
                    ]
                }
            };
        },
        methods: {
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        alert('submit!');
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            }
        }
    }
</script>

<style scoped>
    .el-row {
        background-color: #fafafa;
        height: 100%;
    }

    .el-col {
        /*垂直居中*/
        display: flex;
        justify-content: center;
        align-items: center;
        text-align: center;
        height: 100%;
    }

    .el-divider {
        height: 200px;
    }
</style>

3.获取验证码
在src目录下新建mock.js文件,用于编写随机数据的api,然后我们需要在main.js中引入这个文件:

// 引入mockjs
const Mock = require('mockjs')
// 获取mock.Random对象
const Random = Mock.Random
let Result = {
    code: 200,
    msg: '操作成功',
    data: null
}

/**
 * 用于生成响应数据的函数
 *Mock.mock(url,post/get,function(options));
 * url 表示需要拦截的URL
 * post/get 需要拦截的Ajax请求类型
 */
// 获取验证码图片base64编码以及一个随机码
Mock.mock('/captcha', 'get', () => {
    Result.data = {
        // 获取一个32位随机字符串
        token: Random.string(32),
        // 生成验证码为4y9i1的base64图片编码
        captchaImg: Random.dataImage("120x40", "4y9i1")
    }
    return Result;
})
// 登录
Mock.mock('/login', 'post', () => {

    return Result;
})
  • src/main.js
require("./mock") //引入mock数据

登录页面,获取验证码

getCaptcha() {
    this.$axios.get('/captcha').then(res => {
        this.loginForm.token = res.data.data.token
        this.captchaImg = res.data.data.captchaImg
    })
}

实现登录功能,提交表单后,从Header中获取用户的authorization,也就是含有用户登录信息的jwt,然后提交到store中进行状态管理
this.$store.commit(“SET_TOKEN”, jwt)表示调用store中的SET_TOKEN方法

submitForm(formName) {
    this.$refs[formName].validate((valid) => {
        if (valid) {
            this.$axios.post('/login', this.loginForm).then(res => {
                const jwt = res.headers['authorization']
                // 将jwt存储到应用store中
                this.$store.commit("SET_TOKEN", jwt)
                this.$router.push("/index")
            })
        } else {
            console.log('error submit!!');
            return false;
        }
    });
}

token的状态同步

  • src/store/index.js
export default new Vuex.Store({
    state: {
        token: ''
    },
    getters: {},
    mutations: {
        SET_TOKEN: (state, token) => {
            state.token = token
            localStorage.setItem("token", token)
        }
    },
    actions: {},
    modules: {}
})

4.定义全局axios拦截器,内置拦截和外置拦截
在src目录下新建axios.js文件

import axios from "axios";
import router from "@/router";
import Element from "element-ui"

// axios.defaults.baseURL = "http://localhost:8081"
const request = axios.create({
    timeout: 5000,
    headers: {
        'Content-type': 'application/json;charset=utf-8'
    }
})

request.interceptors.request.use(config => {
    config.headers['Authorization'] = localStorage.getItem("token")
    return config
})

request.interceptors.response.use(response => {
        let res = response.data;
        if (res.code === 200) {
            return response
        } else {
            Element.Message.error(!res.msg ? "系统异常" : res.msg, {duration: 3000})
            return Promise.reject(response.data.msg)
        }
    },
    error => {
        if (error.response.data) {
            error.message = error.response.data.msg
        }

        if (error.response.status === 401) {
            router.push("/login")
        }
        Element.Message.error(error.message, {duration: 3000})
        return Promise.reject(error)
    }
)

export default request

在main.js中引入axios.js文件 import axios from “./axios”
点击登录时,提示如下错误

Mock.mock('/login', 'post', () => {
    Result.code = 401;
    Result.msg = "用户名或验证码错误!!";
    return Result;
})

2、整体布局

1.整体布局、头部布局和左侧导航菜单填充
justify-content:space-around 各个内容间有一定间隔,水平

<template>
    <el-container>
        <el-header>
            <strong>VueAdmin后台管理系统</strong>
            <div class="header-style">
                <el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"></el-avatar>
                <el-dropdown>
                  <span class="el-dropdown-link">
                    admin<i class="el-icon-arrow-down el-icon--right"></i>
                  </span>
                    <el-dropdown-menu slot="dropdown">
                        <el-dropdown-item>个人中心</el-dropdown-item>
                        <el-dropdown-item>我的</el-dropdown-item>
                    </el-dropdown-menu>
                </el-dropdown>
                <el-link href="http://www.baidu.com">百度</el-link>
                <el-link href="https://www.bilibili.com/">B</el-link>
            </div>
        </el-header>
        <el-container>
            <el-aside width="200px">
                <el-menu
                        default-active="2"
                        class="el-menu-vertical-demo"
                        @open="handleOpen"
                        @close="handleClose"
                        background-color="#545c64"
                        text-color="#fff"
                        active-text-color="#ffd04b">
                    <el-menu-item index="Index">
                        <template slot="title">
                            <i class="el-icon-s-home"></i>
                            <span slot="title">首页</span>
                        </template>
                    </el-menu-item>
                    <el-submenu index="1">
                        <template slot="title">
                            <i class="el-icon-s-operation"></i>
                            <span>系统管理</span>
                        </template>
                        <el-menu-item index="1-1">
                            <template slot="title">
                                <i class="el-icon-s-custom"></i>
                                <span slot="title">用户管理</span>
                            </template>
                        </el-menu-item>
                        <el-menu-item index="1-2">
                            <template slot="title">
                                <i class="el-icon-rank"></i>
                                <span slot="title">角色管理</span>
                            </template>
                        </el-menu-item>
                        <el-menu-item index="1-3">
                            <template slot="title">
                                <i class="el-icon-menu"></i>
                                <span slot="title">菜单管理</span>
                            </template>
                        </el-menu-item>
                    </el-submenu>
                    <el-submenu index="2">
                        <template slot="title">
                            <i class="el-icon-s-tools"></i>
                            <span>系统工具</span>
                        </template>
                        <el-menu-item index="2-2">
                            <template slot="title">
                                <i class="el-icon-s-order"></i>
                                <span slot="title">数字字典</span>
                            </template>
                        </el-menu-item>
                    </el-submenu>
                </el-menu>
            </el-aside>
            <el-main>Main</el-main>
        </el-container>
    </el-container>
</template>

<script>
    export default {
        name: "Index",
        methods: {
            handleOpen(key, keyPath) {
                console.log(key, keyPath);
            },
            handleClose(key, keyPath) {
                console.log(key, keyPath);
            }
        }
    }
</script>

<style scoped>
    .el-container {
        width: 100%;
        height: 100%;
        padding: 0;
        margin: 0;
    }

    .header-style {
        float: right;
        width: 210px;
        justify-content: space-around;
        display: flex;
        align-items: center;
    }

    .el-header {
        background-color: #89b5f5;
        color: #333;
        text-align: center;
        line-height: 60px;
    }

    .el-aside {
        background-color: #D3DCE6;
        color: #333;
        text-align: left;
        line-height: 200px;
    }

    .el-main {
        color: #333;
        line-height: 160px;
    }

    .el-dropdown-link {
        cursor: pointer;
    }

    .el-menu-vertical-demo {
        height: 100%;
    }

    a {
        text-decoration: none;
    }
</style>

2.抽取Vue组件
导航栏路由

<router-link to="/menus">
</router-link>

获取用户信息、修改个人密码

getUserInfo() {
    this.$axios.get("/sys/userInfo").then(res => {
        this.userInfo = res.data.data
    })
}
  • mock.js
// 获取用户信息
Mock.mock('/sys/userInfo', 'get', () => {
    Result.data = {
        id: '1',
        username: 'adminTest',
        avatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
    }
    return Result;
})

退出

exitClick() {
    this.$axios.post("/logout").then(res => {
        localStorage.clear()
        sessionStorage.clear()
        this.$store.commit("resetState")
        this.$router.push("/login")
    })
}
  • src/store/index.js
resetState: (state => {
    state.token = ''
})

去掉a标签下划线 text-decoration:none
3.动态菜单栏
1】动态导航

<el-submenu :index="menu.name" v-for="menu in menuList">
    <template slot="title">
        <i :class="menu.icon"></i>
        <span>{{menu.title}}</span>
    </template>
    <router-link :to="item.path" v-for="item in menu.children">
        <el-menu-item :index="item.name">
            <template slot="title">
                <i :class="item.icon"></i>
                <span slot="title">{{item.title}}</span>
            </template>
        </el-menu-item>
    </router-link>
</el-submenu>
data() {
    return {
        menuList: [
            {
                title: '系统管理',
                name: 'SysMange',
                icon: 'el-icon-s-operation',
                path: '',
                children: [
                    {
                        title: '用户管理',
                        name: 'SysUser',
                        icon: 'el-icon-s-custom',
                        path: '/sys/users',
                        children: []
                    }
                ]
            },
            {
                title: '系统工具',
                name: 'SysTools',
                icon: 'el-icon-s-tools',
                path: '',
                children: []
            }
        ]
    }
}

2】动态获取导航
获取导航信息

  • router-index.js
router.beforeEach((to, from, next) => {
    axios.get("/sys/menu/nav", {
        headers: {
            Authorization: localStorage.getItem("token")
        }
    }).then(res => {
        // 获取menuList
        store.commit("setMenuList", res.data.data.nav)
        // 获取权限
        store.commit("setPermList", res.data.data.authority)
        // console.log("********", store.state.menus.menuList)
    })
    next()
})
  • mock.js
// 获取导航信息
Mock.mock('/sys/menu/nav', 'get', () => {
    let nav = [
        {
            title: '系统管理',
            name: 'SysMange',
            icon: 'el-icon-s-operation',
            path: '',
            component: '',
            children: [
                {
                    title: '用户管理',
                    name: 'SysUser',
                    icon: 'el-icon-s-custom',
                    path: '/sys/users',
                    children: [],
                    component: 'sys/User'
                }
            ]
        },
        {
            title: '系统工具',
            name: 'SysTools',
            icon: 'el-icon-s-tools',
            path: '',
            children: [],
            component: ''
        }
    ]
    let authority = []
    Result.data = {
        nav: nav,
        authority: authority
    }
    return Result;
})

将导航列表menuList存储到state中,新建store/modules/menus.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default {
    state: {
        menuList: [],
        permList: []
    },
    getters: {},
    mutations: {
        setMenuList(state, menus) {
            state.menuList = menus
        },

        setPermList(state, perms) {
            state.permList = perms
        }
    },
    actions: {}
}

在store/index.js中引入

import menus from "./modules/menus";

modules: {
     menus
 }

在导航栏页面获取导航信息

computed: {
    menuList: {
        get() {
            return this.$store.state.menus.menuList
        }
    }
}

3】动态绑定路由

  • router-index.js
router.beforeEach((to, from, next) => {
    axios.get("/sys/menu/nav", {
        headers: {
            Authorization: localStorage.getItem("token")
        }
    }).then(res => {
        // 获取menuList
        store.commit("setMenuList", res.data.data.nav)
        // 获取权限
        store.commit("setPermList", res.data.data.authority)
        // console.log("********", store.state.menus.menuList)

        let hasRoutes = store.state.menus.hasRoutes
        if (!hasRoutes) {
            // 动态绑定路由
            let newRoutes = router.options.routes

            res.data.data.nav.forEach(item => {
                if (!item.children) {
                    return null
                }

                item.children.forEach(val => {
                    // 导航转路由
                    let route = menuToRoute(val)
                    // 路由添加到路由管理器中
                    if (route) {
                        newRoutes[0].children.push(route)
                    }
                })
            })
            router.addRoutes(newRoutes)
            hasRoutes = true
            store.commit("changeRouteStatus", hasRoutes)
        }

    })
    next()
})

let menuToRoute = (menu) => {
    let route = {
        path: menu.path,
        name: menu.name,
        meta: {
            title: menu.title,
            icon: menu.icon
        },
        component: () => import('../views/' + menu.component + '.vue')
    }
    return route
}
  • store/modules/menus.js
changeRouteStatus(state, hasRoutes) {
    state.hasRoutes = hasRoutes
}

4】动态标签页
新建Tags页面

<template>
    <el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="tabClick">
        <el-tab-pane
                v-for="(item, index) in editableTabs"
                :key="item.name"
                :label="item.title"
                :name="item.name">
        </el-tab-pane>
    </el-tabs>
</template>

<script>
    export default {
        name: "Tabs",
        data() {
            return {}
        },
        computed: {
            editableTabsValue: {
                get() {
                    return this.$store.state.menus.editableTabsValue
                },
                set(val) {
                    this.$store.state.menus.editableTabsValue = val
                }
            },
            editableTabs: {
                get() {
                    return this.$store.state.menus.editableTabs
                },
                set(val) {
                    this.$store.state.menus.editableTabs = val
                }
            }
        },
        methods: {
            tabClick(target) {
                this.$router.push({name: target.name})
            },

            removeTab(targetName) {
                let tabs = this.editableTabs;
                let activeName = this.editableTabsValue;
                if (activeName === targetName) {
                    tabs.forEach((tab, index) => {
                        if (tab.name === targetName) {
                            let nextTab = tabs[index + 1] || tabs[index - 1];
                            if (nextTab) {
                                activeName = nextTab.name;
                            }
                        }
                    });
                }

                this.editableTabsValue = activeName;
                this.editableTabs = tabs.filter(tab => tab.name !== targetName);
            }
        }
    }
</script>

在Home.vue中引用

<el-main>
    <Tags></Tags>
    <router-view></router-view>
</el-main>

修改导航栏页面SideMenu.vue

// 修改导航栏默认值
:default-active=this.$store.state.menus.editableTabsValue

// 点击事件
// 首页
@click="selectClick({name: 'Index',title: '首页'})"
// other
@click="selectClick(item)"
selectClick(item) {
    this.$store.commit("addTab", item)
}
  • store/modules/menus.js
state: {
    editableTabsValue: 'Index',
    editableTabs: [{
        title: '首页',
        name: 'Index'
    }]
}

addTab(state, tab) {
    let index = state.editableTabs.findIndex(val => val.name === tab.name);
    if (index === -1) {
        state.editableTabs.push({
            title: tab.title,
            name: tab.name
        });
    }
    state.editableTabsValue = tab.name;
}

标签页存在如下debug
1.关闭标签页,子组件内容仍显示

  • Tags.vue removeTab方法
this.$router.push({name: activeName})
// 首页标签不关闭
if (targetName === 'Index') {
    return
}

2.刷新页面,访问路径与标签页内容显示不一致

  • App.vue
watch: {
    $route(to, form) {
        if (to.path != '/login') {
            let obj = {
                name: to.name,
                title: to.meta.title
            }
            this.$store.commit("addTab", obj)
        }
    }
}

3.退出重新登录,标签页仍然存在,清空处理

resetState: (state => {
    state.menuList = []
    state.permList = []
    state.hasRoutes = false
    state.editableTabsValue = 'Index'
    state.editableTabs = [{
        title: '首页',
        name: 'Index'
    }]
})

3、菜单模块

1.菜单页面布局,增删改查功能

<template>
    <div>
        <el-form :inline="true">
            <el-form-item>
                <el-button type="primary" @click="dialogVisible = true">新增</el-button>
            </el-form-item>
        </el-form>
        <el-table
                :data="tableData"
                style="width: 100%;margin-bottom: 20px;"
                row-key="id"
                border
                stripe
                default-expand-all
                :tree-props="{children: 'children', hasChildren: 'hasChildren'}">
            <el-table-column prop="nameCn" label="名称"/>
            <el-table-column prop="code" label="权限编码"/>
            <el-table-column prop="icon" label="图标"/>
            <el-table-column prop="type" label="类型">
                <template slot-scope="scope">
                    <el-tag v-if="scope.row.type == 1">目录</el-tag>
                    <el-tag type="success" v-else-if="scope.row.type == 2">菜单</el-tag>
                    <el-tag type="info" v-else-if="scope.row.type == 3">按钮</el-tag>
                </template>
            </el-table-column>
            <el-table-column prop="path" label="菜单URL"/>
            <el-table-column prop="component" label="菜单组件"/>
            <el-table-column prop="orderNum" label="排序号"/>
            <el-table-column prop="status" label="状态">
                <template slot-scope="scope">
                    <el-tag type="success" v-if="scope.row.status == 1">正常</el-tag>
                    <el-tag type="info" v-else-if="scope.row.status == 0">禁用</el-tag>
                </template>
            </el-table-column>
            <el-table-column prop="operate" label="操作">
                <template slot-scope="scope">
                    <el-button type="text" @click="updateClick(scope.row.id)">修改</el-button>
                    <el-divider direction="vertical"></el-divider>
                    <template>
                        <el-popconfirm title="您确定要删除吗?" @confirm="delClick(scope.row.id)">
                            <el-button slot="reference" type="text">删除</el-button>
                        </el-popconfirm>
                    </template>
                </template>
            </el-table-column>
        </el-table>

        <!-- 新增 -->
        <el-dialog :title="title" :visible.sync="dialogVisible" width="40%">
            <el-form :model="menuForm" :rules="rules" ref="menuForm" label-width="100px">
                <el-form-item label="上级菜单" prop="parentId">
                    <el-select v-model="menuForm.parentId" placeholder="请选择上级菜单">
                        <template v-for="item in tableData">
                            <el-option :label="item.nameCn" :value="item.id"></el-option>
                            <template v-for="child in item.children">
                                <el-option :label="child.nameCn" :value="child.id">
                                    <span>{{'-'+child.nameCn}}</span>
                                </el-option>
                            </template>
                        </template>
                    </el-select>
                </el-form-item>

                <el-form-item label="菜单名称" prop="nameCn">
                    <el-input v-model="menuForm.nameCn"></el-input>
                </el-form-item>

                <el-form-item label="权限编码" prop="code">
                    <el-input v-model="menuForm.code"></el-input>
                </el-form-item>

                <el-form-item label="图标" prop="icon">
                    <el-input v-model="menuForm.icon"></el-input>
                </el-form-item>

                <el-form-item label="菜单URL" prop="path">
                    <el-input v-model="menuForm.path"></el-input>
                </el-form-item>

                <el-form-item label="菜单组件" prop="component">
                    <el-input v-model="menuForm.component"></el-input>
                </el-form-item>

                <el-form-item label="类型" prop="type">
                    <el-radio-group v-model="menuForm.type">
                        <el-radio :label=1>目录</el-radio>
                        <el-radio :label=2>菜单</el-radio>
                        <el-radio :label=3>按钮</el-radio>
                    </el-radio-group>
                </el-form-item>

                <el-form-item label="状态" prop="status">
                    <el-radio-group v-model="menuForm.status">
                        <el-radio :label=1>正常</el-radio>
                        <el-radio :label=0>禁用</el-radio>
                    </el-radio-group>
                </el-form-item>

                <el-row>
                    <el-col :span="12">
                        <el-form-item label="排序号" prop="orderNum">
                            <el-input-number v-model="menuForm.orderNum" :min="0" label="排序号"></el-input-number>
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-form-item>
                    <el-button type="primary" @click="submitForm('menuForm')">提交</el-button>
                    <el-button @click="resetForm('menuForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>
    </div>
</template>

<script>
    export default {
        name: "Menu",
        data() {
            return {
                tableData: [],
                dialogVisible: false,
                title: '新增菜单信息',
                menuForm: {
                    id: '',
                    parentId: '',
                    nameCn: '',
                    code: '',
                    icon: '',
                    path: '',
                    component: '',
                    type: '',
                    status: '',
                    orderNum: '',
                },
                rules: {
                    parentId: [
                        {required: true, message: '上级菜单不能为空!', trigger: 'blur'}
                    ],
                    nameCn: [
                        {required: true, message: '菜单名称不能为空!', trigger: 'blur'}
                    ],
                    code: [
                        {required: true, message: '权限编码不能为空!', trigger: 'blur'}
                    ],
                    type: [
                        {required: true, message: '类型不能为空!', trigger: 'blur'}
                    ],
                    status: [
                        {required: true, message: '状态不能为空!', trigger: 'blur'}
                    ],
                    orderNum: [
                        {required: true, message: '排序号不能为空!', trigger: 'blur'}
                    ]
                }
            }
        },
        created() {
            this.getMenuList();
        },
        methods: {
            getMenuList() {
                this.$axios.get("/sys/menu/list").then(res => {
                    this.tableData = res.data.data
                })
            },
            // 提交操作
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        this.$axios.post("/sys/menu/" + (this.menuForm.id ? "update" : "add")).then(res => {
                            this.resetForm(formName);
                            this.$message({
                                message: '恭喜你,操作成功!',
                                type: 'success',
                                onClose: () => {
                                    this.getMenuList();
                                }
                            });
                            this.dialogVisible = false
                        })
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            // 重置操作
            resetForm(formName) {
                this.$refs[formName].resetFields();
                this.menuForm = {}
                this.dialogVisible = false
            },
            // 修改操作
            updateClick(id) {
                this.$axios.get("/sys/menu/byId" + id).then(res => {
                    this.menuForm = res.data.data
                    this.dialogVisible = true
                    this.title = '修改菜单信息'
                })
            },
            // 删除操作
            delClick(id) {
                this.$axios.post("/sys/menu/delete" + id).then(res => {
                    this.$message({
                        message: '恭喜你,操作成功!',
                        type: 'success',
                        onClose: () => {
                            this.getMenuList();
                        }
                    });
                })
            }
        }
    }
</script>

<style scoped>

</style>

2.mock.js,模拟后端返回数据

// 获取菜单列表
Mock.mock('/sys/menu/list', 'get', () => {
    Result.data = [
        {
            id: '1',
            parentId: '0',
            nameCn: '菜单管理',
            code: 'sys:manage',
            icon: 'el-icon-s-operation',
            path: '',
            component: '',
            type: '1',
            status: '1',
            orderNum: '1',
            children: [
                {
                    id: '2',
                    parentId: '1',
                    nameCn: '用户管理',
                    code: 'sys:user',
                    icon: 'el-icon-s-operation',
                    path: '',
                    component: '',
                    type: '2',
                    status: '1',
                    orderNum: '2',
                    children: []
                },
                {
                    id: '3',
                    parentId: '1',
                    nameCn: '角色管理',
                    code: 'sys:role',
                    icon: 'el-icon-s-operation',
                    path: '',
                    component: '',
                    type: '2',
                    status: '1',
                    orderNum: '3',
                    children: []
                },
                {
                    id: '4',
                    parentId: '1',
                    nameCn: '菜单管理',
                    code: 'sys:menu',
                    icon: 'el-icon-s-operation',
                    path: '',
                    component: '',
                    type: '2',
                    status: '1',
                    orderNum: '4',
                    children: []
                }
            ]
        }
    ]
    return Result;
})
// 新增,更新,删除,使用正则表达式
Mock.mock(RegExp('/sys/menu/*'), 'post', () => {
    return Result;
})
// 通过id获取某条菜单信息
Mock.mock(RegExp('/sys/menu/byId*'), 'get', () => {
    Result.data = {
        id: '2',
        parentId: '1',
        nameCn: '用户管理',
        code: 'sys:user',
        icon: 'el-icon-s-operation',
        path: '',
        component: '',
        type: '2',
        status: '1',
        orderNum: '2',
        children: []
    }
    return Result;
})

4、角色模块

1.角色页面功能,增删改查功能

<template>
    <div>
        <el-form :inline="true">
            <el-form-item>
                <el-input placeholder="名称" clearable prefix-icon="el-icon-search">
                </el-input>
            </el-form-item>
            <el-form-item>
                <el-button @click="getRoleList">搜索</el-button>
                <el-button type="primary" @click="dialogVisible = true">新增</el-button>
                <el-popconfirm title="您确定要删除吗?" @confirm="delClick(null)" style="margin-left: 10px;">
                    <el-button slot="reference" type="danger" :disabled="disabledStatus">批量删除</el-button>
                </el-popconfirm>
            </el-form-item>
        </el-form>

        <el-table
                ref="multipleTable"
                :data="tableData"
                tooltip-effect="dark"
                style="width: 100%"
                border
                stripe
                @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55">
            </el-table-column>
            <el-table-column prop="nameCn" label="名称"/>
            <el-table-column prop="code" label="唯一编码"/>
            <el-table-column prop="describe" label="描述"/>
            <el-table-column prop="status" label="状态">
                <template slot-scope="scope">
                    <el-tag type="success" v-if="scope.row.status == 1">正常</el-tag>
                    <el-tag type="info" v-else-if="scope.row.status == 0">禁用</el-tag>
                </template>
            </el-table-column>
            <el-table-column prop="operate" label="操作">
                <template slot-scope="scope">
                    <el-button type="text" @click="">分配权限</el-button>
                    <el-divider direction="vertical"></el-divider>

                    <el-button type="text" @click="updateClick(scope.row.id)">修改</el-button>
                    <el-divider direction="vertical"></el-divider>
                    <template>
                        <el-popconfirm title="您确定要删除吗?" @confirm="delClick(scope.row.id)">
                            <el-button slot="reference" type="text">删除</el-button>
                        </el-popconfirm>
                    </template>
                </template>
            </el-table-column>
        </el-table>

        <!-- 新增 -->
        <el-dialog :title="title" :visible.sync="dialogVisible" width="40%">
            <el-form :model="roleForm" :rules="rules" ref="roleForm" label-width="100px">
                <el-form-item label="角色名称" prop="nameCn">
                    <el-input v-model="roleForm.nameCn"></el-input>
                </el-form-item>
                <el-form-item label="唯一编码" prop="code">
                    <el-input v-model="roleForm.code"></el-input>
                </el-form-item>
                <el-form-item label="描述" prop="describe">
                    <el-input v-model="roleForm.describe"></el-input>
                </el-form-item>
                <el-form-item label="状态" prop="status">
                    <el-radio-group v-model="roleForm.status">
                        <el-radio :label=1>正常</el-radio>
                        <el-radio :label=0>禁用</el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="submitForm('roleForm')">提交</el-button>
                    <el-button @click="resetForm('roleForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>

        <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current=current
                :page-sizes="[10, 20, 50, 100]"
                :page-size=size
                layout="total, sizes, prev, pager, next, jumper"
                :total=total>
        </el-pagination>
    </div>
</template>

<script>
    export default {
        name: "Role",
        data() {
            return {
                roleForm: {},
                disabledStatus: true,
                dialogVisible: false,
                title: '新增角色信息',
                tableData: [],
                current: 1,
                size: 10,
                total: 0,
                multipleSelection: [],
                rules: {
                    nameCn: [
                        {required: true, message: '角色名称不能为空!', trigger: 'blur'}
                    ],
                    code: [
                        {required: true, message: '唯一编码不能为空!', trigger: 'blur'}
                    ],
                    status: [
                        {required: true, message: '状态不能为空!', trigger: 'blur'}
                    ]
                }
            }
        },
        created() {
            this.getRoleList();
        },
        methods: {
            toggleSelection(rows) {
                if (rows) {
                    rows.forEach(row => {
                        this.$refs.multipleTable.toggleRowSelection(row);
                    });
                } else {
                    this.$refs.multipleTable.clearSelection();
                }
            },
            handleSelectionChange(val) {
                this.multipleSelection = val;
                this.disabledStatus = val.length == 0
            },
            getRoleList() {
                this.$axios.get("/sys/role/list", {
                    params: {
                        nameCn: this.roleForm.nameCn,
                        current: this.current,
                        size: this.size
                    }
                }).then(res => {
                    this.tableData = res.data.data.record
                    this.current = res.data.data.current
                    this.size = res.data.data.size
                    this.total = res.data.data.total
                })
            },
            // 提交操作
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        this.$axios.post("/sys/role/" + (this.roleForm.id ? "update" : "add")).then(res => {
                            this.resetForm(formName);
                            this.$message({
                                message: '恭喜你,操作成功!',
                                type: 'success',
                                onClose: () => {
                                    this.getRoleList();
                                }
                            });
                            this.dialogVisible = false
                        })
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            // 重置操作
            resetForm(formName) {
                this.$refs[formName].resetFields();
                this.roleForm = {}
                this.dialogVisible = false
            },
            // 修改操作
            updateClick(id) {
                this.$axios.get("/sys/role/byId" + id).then(res => {
                    this.roleForm = res.data.data
                    this.dialogVisible = true
                })
            },
            // 删除操作
            delClick(id) {
                let ids = []
                if (id) {
                    ids.push(id)
                } else {
                    this.multipleSelection.forEach(item => {
                        ids.push(item.id)
                    })
                }
                console.log("***ids: ", ids)
                this.$axios.post("/sys/role/delete", ids).then(res => {
                    this.$message({
                        message: '恭喜你,操作成功!',
                        type: 'success',
                        onClose: () => {
                            this.getRoleList();
                        }
                    });
                })
            },

            handleSizeChange(val) {
                console.log(`每页 ${val} 条`);
                this.size = val
                this.getRoleList();
            },
            handleCurrentChange(val) {
                console.log(`当前页: ${val}`);
                this.current = val
                this.getRoleList();
            }
        }
    }
</script>

<style scoped>
    .el-pagination {
        float: right;
        padding-top: 20px;
    }
</style>

2.mock.js,模拟后端返回数据

// 获取角色列表
Mock.mock(RegExp('/sys/role/list'), 'get', () => {
    Result.data = {
        "record": [
            {
                id: 1,
                nameCn: '普通用户',
                code: 'normal',
                describe: '查看功能',
                status: '1'
            }, {
                id: 2,
                nameCn: '超级管理员',
                code: 'admin',
                describe: '系统默认最高权限',
                status: '1'
            }
        ],
        current: 1,
        size: 10,
        total: 2
    }
    return Result;
})

// 新增,更新,删除,使用正则表达式
Mock.mock(RegExp('/sys/role/*'), 'post', () => {
    return Result;
})
// 通过id获取某条角色信息
Mock.mock(RegExp('/sys/role/byId*'), 'get', () => {
    Result.data = {
        id: 2,
        nameCn: '超级管理员',
        code: 'admin',
        describe: '系统默认最高权限',
        status: '1'
    }
    return Result;
})

3.分配权限

// 触发事件
<el-button type="text" @click="permClick(scope.row.id)">分配权限</el-button>

<!-- 分配权限对话框 -->
<el-dialog title="分配权限" :visible.sync="permDialogVisible" width="40%">
    <el-form :model="permForm" :rules="rules" ref="roleForm" label-width="100px">
        <!-- 父子不相互关联 check-strictly=true-->
        <el-tree
                :data="permData"
                show-checkbox
                node-key="id"
                ref="permTree"
                :default-expand-all=true
                :check-strictly=true
                :props="defaultProps">
        </el-tree>
    </el-form>

    <span slot="footer">
            <el-button @click="permDialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="submitPermForm('permForm')">确 定</el-button>
        </span>
</el-dialog>
// 定义相关变量
data() {
    return {
        permDialogVisible: false,
        permForm: {},
        permData: [],
        defaultProps: {
            children: 'children',
            label: 'nameCn'
        }
    }
},

methods:{
	// 分配权限
   permClick(id) {
       this.permDialogVisible = true
       this.$axios.get("/sys/role/info" + id).then(res => {
           this.$refs.permTree.setCheckedKeys(res.data.data.menuIds);

           this.permForm = res.data.data
       })
   },

  // 分配权限提交操作
  submitPermForm(formName) {
      const menuIds = this.$refs.permTree.getCheckedKeys()
      console.log("*****", menuIds)
      this.$axios.post("/sys/role/addPerm" + this.permForm.id, menuIds).then(res => {
          this.$message({
              message: '恭喜你,操作成功!',
              type: 'success',
              onClose: () => {
                  this.getRoleList();
              }
          });
          this.permDialogVisible = false
      })
  }
}

4、用户管理

1.用户信息管理

<template>
    <div>
        <el-form :inline="true" :model="searchForm">
            <el-form-item prop="username">
                <el-input placeholder="名称" clearable v-model="searchForm.username" prefix-icon="el-icon-search">
                </el-input>
            </el-form-item>
            <el-form-item>
                <el-button @click="getUserList">搜索</el-button>
                <el-button type="primary" @click="dialogVisible = true">新增</el-button>
                <el-popconfirm title="您确定要删除吗?" @confirm="delClick(null)" style="margin-left: 10px;">
                    <el-button slot="reference" type="danger" :disabled="disabledStatus">批量删除</el-button>
                </el-popconfirm>
            </el-form-item>
        </el-form>

        <el-table
                ref="multipleTable"
                :data="tableData"
                tooltip-effect="dark"
                style="width: 100%"
                border
                stripe
                @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55">
            </el-table-column>
            <el-table-column prop="avatar" label="头像" width="60">
                <template slot-scope="scope">
                    <el-avatar size="medium" :src="scope.row.avatar"></el-avatar>
                </template>
            </el-table-column>
            <el-table-column prop="username" label="用户名"/>
            <el-table-column prop="userRole" label="用户角色">
                <template slot-scope="scope">
                    <el-tag type="success" size="small" v-for="item in scope.row.userRole">{{item.name}}</el-tag>
                </template>
            </el-table-column>
            <el-table-column prop="email" label="邮箱"/>
            <el-table-column prop="mobile" label="手机号"/>
            <el-table-column prop="status" label="状态">
                <template slot-scope="scope">
                    <el-tag type="success" v-if="scope.row.status == 1">正常</el-tag>
                    <el-tag type="info" v-else-if="scope.row.status == 0">禁用</el-tag>
                </template>
            </el-table-column>
            <el-table-column prop="createTime" label="创建时间"></el-table-column>
            <el-table-column prop="operate" label="操作">
                <template slot-scope="scope">
                    <el-button type="text" @click="roleClick(scope.row.id)">分配角色</el-button>
                    <el-divider direction="vertical"></el-divider>

                    <el-button type="text" @click="resetPwdClick(scope.row.id,scope.row.username)">重置密码</el-button>
                    <el-divider direction="vertical"></el-divider>

                    <el-button type="text" @click="updateClick(scope.row.id)">修改</el-button>
                    <el-divider direction="vertical"></el-divider>
                    <template>
                        <el-popconfirm title="您确定要删除吗?" @confirm="delClick(scope.row.id)">
                            <el-button slot="reference" type="text">删除</el-button>
                        </el-popconfirm>
                    </template>
                </template>
            </el-table-column>
        </el-table>

        <!-- 新增 -->
        <el-dialog title="新增用户信息" :visible.sync="dialogVisible" width="40%">
            <el-form :model="userForm" :rules="rules" ref="userForm" label-width="100px">
                <el-form-item label="用户名" prop="username">
                    <el-input v-model="userForm.username"></el-input>
                </el-form-item>
                <el-form-item label="邮箱" prop="email">
                    <el-input v-model="userForm.email"></el-input>
                </el-form-item>
                <el-form-item label="手机号" prop="mobile">
                    <el-input v-model="userForm.mobile"></el-input>
                </el-form-item>
                <el-form-item label="状态" prop="status">
                    <el-radio-group v-model="userForm.status">
                        <el-radio :label=1>正常</el-radio>
                        <el-radio :label=0>禁用</el-radio>
                    </el-radio-group>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="submitForm('userForm')">提交</el-button>
                    <el-button @click="resetForm('userForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </el-dialog>

        <!-- 分配角色对话框 -->
        <el-dialog title="分配角色" :visible.sync="roleDialogVisible" width="40%">
            <el-form :model="roleForm" :rules="rules" ref="userForm" label-width="100px">
                <!-- 父子不相互关联 check-strictly=true-->
                <el-tree
                        :data="roleData"
                        show-checkbox
                        node-key="id"
                        ref="roleTree"
                        :default-expand-all=true
                        :props="defaultProps">
                </el-tree>
            </el-form>

            <span slot="footer">
                    <el-button @click="roleDialogVisible = false">取 消</el-button>
                    <el-button type="primary" @click="submitroleForm('roleForm')">确 定</el-button>
                </span>
        </el-dialog>

        <el-pagination
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :current=current
                :page-sizes="[10, 20, 50, 100]"
                :page-size=size
                layout="total, sizes, prev, pager, next, jumper"
                :total=total>
        </el-pagination>
    </div>
</template>

<script>
    export default {
        name: "User",
        data() {
            return {
                circleUrl: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
                imageUrl: '',
                searchForm: {
                    username: ''
                },
                userForm: {
                    id: '',
                    avatar: '',
                    username: '',
                    userRole: '',
                    email: '',
                    mobile: '',
                    status: '',
                    createTime: ''
                },
                disabledStatus: true,
                dialogVisible: false,
                tableData: [],
                current: 1,
                size: 10,
                total: 0,
                multipleSelection: [],
                roleDialogVisible: false,
                roleForm: {},
                roleData: [],
                defaultProps: {
                    children: 'children',
                    label: 'nameCn'
                },
                checkList: [],
                rules: {
                    username: [
                        {required: true, message: '用户名不能为空!', trigger: 'blur'}
                    ],
                    email: [
                        {required: true, message: '邮箱不能为空!', trigger: 'blur'}
                    ],
                    mobile: [
                        {required: true, message: '手机号不能为空!', trigger: 'blur'}
                    ],
                    status: [
                        {required: true, message: '状态不能为空!', trigger: 'blur'}
                    ]
                }
            }
        },
        created() {
            this.getUserList();

            this.$axios.get("/sys/role/list").then(res => {
                this.roleData = res.data.data.record
            })
        },
        methods: {
            toggleSelection(rows) {
                if (rows) {
                    rows.forEach(row => {
                        this.$refs.multipleTable.toggleRowSelection(row);
                    });
                } else {
                    this.$refs.multipleTable.clearSelection();
                }
            },
            handleSelectionChange(val) {
                this.multipleSelection = val;
                this.disabledStatus = val.length == 0
            },
            getUserList() {
                this.$axios.get("/sys/user/list", {
                    params: {
                        username: this.userForm.username,
                        current: this.current,
                        size: this.size
                    }
                }).then(res => {
                    this.tableData = res.data.data.record
                    this.current = res.data.data.current
                    this.size = res.data.data.size
                    this.total = res.data.data.total
                })
            },
            // 提交操作
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        this.$axios.post("/sys/user/" + (this.userForm.id ? "update" : "add")).then(res => {
                            this.resetForm(formName);
                            this.$message({
                                message: '恭喜你,操作成功!',
                                type: 'success',
                                onClose: () => {
                                    this.getUserList();
                                }
                            });
                            this.dialogVisible = false
                        })
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            // 重置操作
            resetForm(formName) {
                this.$refs[formName].resetFields();
                this.userForm = {}
                this.dialogVisible = false
            },
            // 修改操作
            updateClick(id) {
                this.$axios.get("/sys/user/byId" + id).then(res => {
                    this.userForm = res.data.data
                    this.dialogVisible = true
                })
            },
            // 删除操作
            delClick(id) {
                let ids = []
                if (id) {
                    ids.push(id)
                } else {
                    this.multipleSelection.forEach(item => {
                        ids.push(item.id)
                    })
                }
                console.log("***ids: ", ids)
                this.$axios.post("/sys/user/delete", ids).then(res => {
                    this.$message({
                        message: '恭喜你,操作成功!',
                        type: 'success',
                        onClose: () => {
                            this.getUserList();
                        }
                    });
                })
            },

            handleSizeChange(val) {
                console.log(`每页 ${val} 条`);
                this.size = val
                this.getUserList();
            },
            handleCurrentChange(val) {
                console.log(`当前页: ${val}`);
                this.current = val
                this.getUserList();
            },

            // 分配角色
            roleClick(id) {
                this.roleDialogVisible = true
                this.$axios.get("/sys/role/info" + id).then(res => {
                    this.$refs.roleTree.setCheckedKeys(res.data.data.menuIds);

                    this.roleForm = res.data.data
                })
            },

            // 分配角色提交操作
            submitroleForm(formName) {
                const menuIds = this.$refs.roleTree.getCheckedKeys()
                console.log("*****", menuIds)
                this.$axios.post("/sys/user/addRole" + this.roleForm.id, menuIds).then(res => {
                    this.$message({
                        message: '恭喜你,操作成功!',
                        type: 'success',
                        onClose: () => {
                            this.getUserList();
                        }
                    });
                    this.roleDialogVisible = false
                })
            },

            resetPwdClick(id, username) {
                this.$confirm('将重置用户【' + username + '】的密码,是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    this.$axios.post("/sys/user/resetPwd", id).then(res => {
                        this.$message({
                            type: 'success',
                            message: '恭喜你,操作成功!',
                            onClose: () => {
                                this.getUserList();
                            }
                        });
                    });
                }).catch(() => {
                    this.$message({
                        type: 'info',
                        message: '操作已取消!'
                    });
                });
            }
        }
    }
</script>

<style scoped>
    .el-pagination {
        float: right;
        padding-top: 20px;
    }

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

    .avatar-uploader .el-upload:hover {
        border-color: #409EFF;
    }

    .avatar-uploader-icon {
        font-size: 28px;
        color: #8c939d;
        width: 178px;
        height: 178px;
        line-height: 178px;
        text-align: center;
    }

    .avatar {
        width: 178px;
        height: 178px;
        display: block;
    }
</style>

2.mock.js,模拟后端返回数据

// 获取用户列表
Mock.mock(RegExp('/sys/user/list'), 'get', () => {
    Result.data = {
        "record": [
            {
                id: 1,
                avatar: '',
                username: '张三',
                userRole: '1',
                email: 'zhangsan@qq.com',
                mobile: '18909129012',
                status: '1',
                createTime: '2022-12-09 10:12:58'
            }, {
                id: 2,
                avatar: '',
                username: '李四',
                userRole: '2',
                email: 'lisi@qq.com',
                mobile: '19090129012',
                status: '1',
                createTime: '2022-12-09 10:12:58'
            }
        ],
        current: 1,
        size: 10,
        total: 2
    }
    return Result;
})

// 新增,更新,删除,使用正则表达式
Mock.mock(RegExp('/sys/user/*'), 'post', () => {
    return Result;
})
// 通过id获取某条用户信息
Mock.mock(RegExp('/sys/user/byId*'), 'get', () => {
    Result.data = {
        id: 2,
        avatar: '',
        username: '李四',
        userRole: '2',
        email: 'lisi@qq.com',
        mobile: '19090129012',
        status: '1',
        createTime: '2022-12-09 10:12:58',
    }
    return Result;
})

3.按钮权限的控制
1】定义全局方法

  • src/globalFun.js
import Vue from 'vue'

Vue.mixin({
    methods: {
        hasRoute(perm) {
            const authority = this.$store.state.menus.permList

            return authority.indexOf(perm) > -1
        }
    }
})
  • main.js中引入
import global from "./globalFun"

全局方法hasRoute调用,返回true,按钮显示,否则不显示

 <el-button type="primary" @click="dialogVisible = true" v-if="hasRoute('sys:user:save')">新增</el-button>
Logo

前往低代码交流专区

更多推荐