1、MOCK封装
1.1、在mock目录下新建modules文件夹.并创建menu.js
// 获取菜单树
const menuTreeData = {
"code": 200,
"msg": '成功',
"data": [
{
"menuId": 1,
"parentId": 0,
"name": "系统管理",
"url": null,
"perms": null,
"type": 0,
"icon": "el-icon-setting",
"orderNum": 0,
"level": 0,
"children": [
{
"menuId": 2,
"parentId": 1,
"name": "微信用户",
"parentName": "系统管理",
"url": "/miniUser",
"perms": null,
"type": 1,
"icon": "el-icon-service",
"orderNum": 1,
"delFlag": 0,
"level": 1,
"children": [{
"menuId": 4,
"parentId": 2,
"name": "删除",
"parentName": "微信用户",
"url": null,
"perms": 'wx:miniUser:del',
"type": 2,
"icon": null,
"orderNum": 5,
"delFlag": 0,
"level": 2
}]
},
{
"menuId": 3,
"parentId": 1,
"name": "菜单管理",
"parentName": "系统管理",
"url": "/menu",
"perms": null,
"type": 1,
"icon": "el-icon-news",
"orderNum": 2,
"delFlag": 0,
"level": 1,
"children": []
}
]
}
]
}
export function getMenu() {
//console.log('menu.json');
return {
url: '/menu.json',
type: 'get',
data: menuTreeData
}
}
1.2、将mock/index.js进行封装
本人参考的是博客园的朝雨忆轻尘:
工具模块封装
import Mock from "mockjs";
import * as menu from "./modules/menu";
// 1. 开启/关闭[所有模块]拦截, 通过调[openMock参数]设置.
// 2. 开启/关闭[业务模块]拦截, 通过调用fnCreate方法[isOpen参数]设置.
// 3. 开启/关闭[业务模块中某个请求]拦截, 通过函数返回对象中的[isOpen属性]设置.
// let openMock = true
let openMock = true;
fnCreate(menu, openMock);
/**
* 创建mock模拟数据
* @param {*} mod 模块
* @param {*} isOpen 是否开启?
*/
function fnCreate(mod, isOpen = true) {
if (isOpen) {
for (var key in mod) {
(res => {
if (res.isOpen !== false) {
let url = process.env.BASE_API + res.url;
Mock.mock(new RegExp(url), res.type, opts => {
delete opts.body;
console.log("\n");
console.log("%cmock拦截, 请求: ", "color:blue", opts);
console.log("%cmock拦截, 响应: ", "color:blue", res.data);
return res.data;
});
}
})(mod[key]() || {});
}
}
}
这样以后开关、封装都会非常的方便
2、整合axios的http请求
2.1、src下新建api目录并创建axios.js
统一配置封装axios的请求.详情看代码
/*src/api/axios.js*/
import axios from "axios";
export default function $axios(options) {
return new Promise((resolve, reject) => {
const service = axios.create({
baseURL: process.env.BASE_API,// src/config目录下的dev和prod两个文件里配置
method: "get",
timeout: 10000,//请求超时时间
responseType: "json" // 返回数据类型
});
// request拦截器
service.interceptors.request.use(
config => {
config.headers["xxx-token"] = 'xiaobusi'; // 让每个请求携带自定义token 这里先写死请根据实际情况自行修改
config.headers["Content-Type"] = "application/json";
return config;
},
error => {
// Do something with request error
console.log(error); // for debug
Promise.reject(error);
}
);
// 请求处理
service(options)
.then(res => {
resolve(res);
return false;
})
.catch(error => {
reject(error);
});
});
}
2.2、封装各种模块并整合挂载到Vue的原型链上
2.2.1、新建modules目录并创建menu.js
/*src/api/modules/menu.js*/
import axios from '../axios'
/*
* 菜单管理模块
*/
export const getMenu = () => {
return axios({
url: '/menu.json',
method: 'get'
})
}
2.2.2、新建api.js加载所有模块并导出
/*src/api/api.js*/
/*
* 接口统一集成模块
*/
import * as menu from './modules/menu'
// 默认全部导出
export default {
menu
}
2.2.3、新建index.js.并所有api请求挂载到Vue上
/*src/api/index.js*/
// 导入所有接口
import apis from './api'
const install = Vue => {
if (install.installed)
return;
install.installed = true;
Object.defineProperties(Vue.prototype, {
// 注意,此处挂载在 Vue 原型的 $api 对象上
$api: {
get() {
return apis
}
}
})
}
export default install
开始测试
<script>
import axios from 'axios';
import Mock from '@/mock';
export default {
name: 'HelloWorld',
methods:{
getMenu(){
this.$api.menu.getMenu().then(res=>{
alert(JSON.stringify(res.data));
});
}
}
}
</script>
3、整合页面菜单导航
3.1、src下新建views目录
主要还是参考Element布局容器
<el-container>
:外层容器。当子元素中包含 <el-header>
或 <el-footer>
时,全部子元素会垂直上下排列,否则会水平左右排列。
<el-header>
:顶栏容器。
<el-aside>
:侧边栏容器。
<el-main>
:主要区域容器。
<el-footer>
:底栏容器。
在views下新建Home和目录layout并新建Header、Menu、Footer
views/Home.vue
<template>
<el-container style="height: 100%; border: 1px solid #eee">
<el-header style="text-align: right; font-size: 12px">
<Header></Header>
</el-header>
<el-container>
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
<Menu></Menu>
</el-aside>
<el-container>
<el-main>
<router-view></router-view>
</el-main>
<el-footer>
<Footer></Footer>
</el-footer>
</el-container>
</el-container>
</el-container>
</template>
<script>
import Mock from '@/mock';
import Menu from "@/views/layout/Menu";
import Header from "@/views/layout/Header";
import Footer from "@/views/layout/Footer";
export default {
name: "Home",
components: {
Menu,
Header,
Footer
}
};
</script>
<style lang='scss'>
.el-header {
background-color: #b3c0d1;
color: #333;
line-height: 60px;
}
.el-aside {
color: #333;
}
</style>
views/layout/Header.vue
<template>
<div>
<el-dropdown>
<i class="el-icon-setting" style="margin-right: 15px"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>查看</el-dropdown-item>
<el-dropdown-item>新增</el-dropdown-item>
<el-dropdown-item>删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span>王小虎</span>
</div>
</template>
views/layout/Footer.vue
<template>
<span>Footer</span>
</template>
views/layout/Menu.vue
<template>
<!-- 导航菜单 -->
<el-menu :default-openeds="['1', '3']">
<!-- 导航菜单树组件,动态加载菜单 -->
<el-menu-item-group v-for="menu in menuTrees" :key="menu.menuId">
<el-submenu v-if="menu.children && menu.children.length >= 1" :index="menu.menuId + ''">
<template slot="title">
<i :class="menu.icon"></i>
<span slot="title">{{menu.name}}</span>
</template>
<el-menu-item v-for="item in menu.children" :key="item.menuId" :index="item.menuId + ''" @click="handleRoute(item)">
<i :class="item.icon"></i>
<span slot="title">{{item.name}}</span>
</el-menu-item>
</el-submenu>
<el-menu-item v-else :index="menu.menuId + ''" @click="handleRoute(menu)">
<i :class="menu.icon"></i>
<span slot="title">{{menu.name}}</span>
</el-menu-item>
</el-menu-item-group>
</el-menu>
</template>
<script>
export default {
data() {
return {
menuTrees: []
};
},
methods: {
handlerMenu: function() {
this.$api.menu.getMenu().then(res => {
this.menuTrees = res.data;
});
},
handleRoute(menu) {
// 通过菜单URL跳转至指定路由
this.$router.push("/");
this.$router.push(menu.url);
}
},
mounted() {
this.handlerMenu();
}
};
</script>
添加菜单对应的路由
@/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Home,
children: [
{ path: "test", component: () => import('@/views/test/index'), name: "test" },
{ path: "miniUser", component: () => import('@/views/miniUser/index'), name: "miniUser" },
{ path: "menu", component: () => import('@/views/system/menu/index'), name: "menu" }
]
}
]
})
<font color=#DC143C>注意:</font>
Home.vue中的<router-view></router-view>
是一个嵌套组件
router/index.js中home下的children组件就会嵌套在里面
3.2、整合菜单
3.2.1、新建模块@/views/core/TableTreeColumn
<template>
<div class="container" style="width:99%;margin-top:-25px;">
<!--工具栏-->
<div class="toolbar" style="float:left;padding-top:10px;padding-left:15px;">
<el-form :inline="true" :model="filters" :size="size">
<el-form-item>
<el-input v-model="filters.name" placeholder="名称"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="search">查询</el-button>
</el-form-item>
<el-form-item>
<el-button @click="search">重置</el-button>
</el-form-item>
</el-form>
</div>
<!--表格树内容栏-->
<el-table
:data="tableTreeDdata"
stripe
size="mini"
style="width: 100%;"
v-loading="loading"
element-loading-text="拼命加载中"
>
<el-table-column prop="menuId" header-align="center" align="center" width="80" label="ID"></el-table-column>
<TableTreeColumn prop="name" header-align="center" treeKey="menuId" width="150" label="名称"></TableTreeColumn>
<el-table-column header-align="center" align="center" label="图标">
<template slot-scope="scope">
<i :class="scope.row.icon || ''"></i>
</template>
</el-table-column>
<el-table-column prop="type" header-align="center" align="center" label="类型">
<template slot-scope="scope">
<el-tag v-if="scope.row.type === 0" size="small">目录</el-tag>
<el-tag v-else-if="scope.row.type === 1" size="small" type="success">菜单</el-tag>
<el-tag v-else-if="scope.row.type === 2" size="small" type="info">按钮</el-tag>
</template>
</el-table-column>
<el-table-column
prop="parentName"
header-align="center"
align="center"
width="120"
label="上级菜单"
></el-table-column>
<el-table-column
prop="url"
header-align="center"
align="center"
width="150"
:show-overflow-tooltip="true"
label="菜单URL"
></el-table-column>
<el-table-column
prop="perms"
header-align="center"
align="center"
width="150"
:show-overflow-tooltip="true"
label="授权标识"
></el-table-column>
<el-table-column prop="orderNum" header-align="center" align="center" label="排序"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="240" label="操作">
<template slot-scope="scope">
<el-row>
<el-button icon="el-icon-edit" @click="handleEdit(scope.row)" round>修改</el-button>
<el-button icon="el-icon-delete" type="danger" @click="handleDelete(scope.row)" round>删除</el-button>
</el-row>
</template>
</el-table-column>
</el-table>
<!-- 新增修改界面 -->
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
width="40%"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="submitForm()"
label-width="80px"
:size="size"
style="text-align:left;"
>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="dataForm.type">
<el-radio v-for="(type, index) in menuTypeList" :label="index" :key="index">{{ type }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="menuTypeList[dataForm.type] + '名称'" prop="name">
<el-input v-model="dataForm.name" :placeholder="menuTypeList[dataForm.type] + '名称'"></el-input>
</el-form-item>
<el-form-item label="上级菜单" prop="parentName">
<PopupTreeInput
:data="popupTreeData"
:props="popupTreeProps"
:prop="dataForm.parentName==null?'根节点':dataForm.parentName"
:nodeKey="''+dataForm.parentId"
:currentChangeHandle="handleTreeSelectChange"
></PopupTreeInput>
</el-form-item>
<el-form-item v-if="dataForm.type === 1" label="菜单路由" prop="url">
<el-input v-model="dataForm.url" placeholder="菜单路由"></el-input>
</el-form-item>
<el-form-item v-if="dataForm.type !== 0" label="授权标识" prop="perms">
<el-input
v-model="dataForm.perms"
placeholder="如: sys:user:add, sys:user:edit, sys:user:delete"
></el-input>
</el-form-item>
<el-form-item v-if="dataForm.type !== 2" label="排序编号" prop="orderNum">
<el-input-number
v-model="dataForm.orderNum"
controls-position="right"
:min="0"
label="排序编号"
></el-input-number>
</el-form-item>
<el-form-item v-if="dataForm.type !== 2" label="菜单图标" prop="icon">
<el-input v-model="dataForm.icon" placeholder="请输入菜单图标"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button :size="size" @click="dialogVisible = false">取消</el-button>
<el-button :size="size" type="primary" @click="submitForm()">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import TableTreeColumn from "@/views/core/TableTreeColumn";
import PopupTreeInput from "@/components/PopupTreeInput";
export default {
components: { TableTreeColumn,PopupTreeInput},
data() {
return {
size: "small",
loading: false,
filters: {
name: ""
},
tableTreeDdata: [],
dialogVisible: false,
menuTypeList: ["目录", "菜单", "按钮"],
dataForm: {
id: 0,
type: 1,
name: "",
parentId: 0,
parentName: "",
url: "",
perms: "",
orderNum: 0,
icon: "",
iconList: []
},
dataRule: {
name: [
{ required: true, message: "菜单名称不能为空", trigger: "blur" }
],
parentName: [
{ required: true, message: "上级菜单不能为空", trigger: "change" }
]
},
popupTreeData: [],
popupTreeProps: {
label: "name",
children: "children"
}
};
},
methods: {
search: function() {},
// 获取数据
findTreeData: function() {
this.loading = true;
this.$api.menu.getMenu().then(res => {
this.tableTreeDdata = res.data;
this.popupTreeData = this.getParentMenuTree(res.data);
this.loading = false;
});
},
// 获取上级菜单树
getParentMenuTree: function(tableTreeDdata) {
let parent = {
parentId: -1,
name: "根节点",
children: tableTreeDdata
};
return [parent];
},
// 显示新增界面
handleAdd: function() {
this.dialogVisible = true;
this.dataForm = {
id: 0,
type: 1,
typeList: ["目录", "菜单", "按钮"],
name: "",
parentId: 0,
parentName: "",
url: "",
perms: "",
orderNum: 0,
icon: "",
iconList: []
};
},
// 显示编辑界面
handleEdit: function(row) {
this.dialogVisible = true;
Object.assign(this.dataForm, row);
},
// 删除
handleDelete(row) {
this.$confirm("确认删除选中记录吗?", "提示", {
type: "warning"
}).then(() => {
let params = this.getDeleteIds([], row);
this.$api.menu.batchDelete(params).then(res => {
this.findTreeData();
this.$message({ message: "删除成功", type: "success" });
});
});
},
// 获取删除的包含子菜单的id列表
getDeleteIds(ids, row) {
ids.push({ id: row.id });
if (row.children != null) {
for (let i = 0, len = row.children.length; i < len; i++) {
this.getDeleteIds(ids, row.children[i]);
}
}
return ids;
},
// 菜单树选中
handleTreeSelectChange(data, node) {
this.dataForm.parentId = data.id;
this.dataForm.parentName = data.name;
},
// 图标选中
iconActiveHandle(iconName) {
this.dataForm.icon = iconName;
},
// 表单提交
submitForm() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
this.$confirm("确认提交吗?", "提示", {}).then(() => {
this.editLoading = true;
let params = Object.assign({}, this.dataForm);
this.$api.menu.save(params).then(res => {
if (res.code == 200) {
this.$message({ message: "操作成功", type: "success" });
} else {
this.$message({
message: "操作失败, " + res.msg,
type: "error"
});
}
this.editLoading = false;
this.$refs["dataForm"].resetFields();
this.dialogVisible = false;
this.findTreeData();
});
});
}
});
}
},
mounted() {
this.findTreeData();
}
};
</script>
<style scoped>
</style>
3.2.1、新建菜单页views/system/menu/index.vue
<template>
<div class="container" style="width:99%;margin-top:-25px;">
<!--工具栏-->
<div class="toolbar" style="float:left;padding-top:10px;padding-left:15px;">
<el-form :inline="true" :model="filters" :size="size">
<el-form-item>
<el-input v-model="filters.name" placeholder="名称"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="search">查询</el-button>
</el-form-item>
<el-form-item>
<el-button @click="search">重置</el-button>
</el-form-item>
</el-form>
</div>
<!--表格树内容栏-->
<el-table
:data="tableTreeDdata"
stripe
size="mini"
style="width: 100%;"
v-loading="loading"
element-loading-text="拼命加载中"
>
<el-table-column prop="menuId" header-align="center" align="center" width="80" label="ID"></el-table-column>
<TableTreeColumn prop="name" header-align="center" treeKey="menuId" width="150" label="名称"></TableTreeColumn>
<el-table-column header-align="center" align="center" label="图标">
<template slot-scope="scope">
<i :class="scope.row.icon || ''"></i>
</template>
</el-table-column>
<el-table-column prop="type" header-align="center" align="center" label="类型">
<template slot-scope="scope">
<el-tag v-if="scope.row.type === 0" size="small">目录</el-tag>
<el-tag v-else-if="scope.row.type === 1" size="small" type="success">菜单</el-tag>
<el-tag v-else-if="scope.row.type === 2" size="small" type="info">按钮</el-tag>
</template>
</el-table-column>
<el-table-column
prop="parentName"
header-align="center"
align="center"
width="120"
label="上级菜单"
></el-table-column>
<el-table-column
prop="url"
header-align="center"
align="center"
width="150"
:show-overflow-tooltip="true"
label="菜单URL"
></el-table-column>
<el-table-column
prop="perms"
header-align="center"
align="center"
width="150"
:show-overflow-tooltip="true"
label="授权标识"
></el-table-column>
<el-table-column prop="orderNum" header-align="center" align="center" label="排序"></el-table-column>
<el-table-column fixed="right" header-align="center" align="center" width="240" label="操作">
<template slot-scope="scope">
<el-row>
<el-button icon="el-icon-edit" @click="handleEdit(scope.row)" round>修改</el-button>
<el-button icon="el-icon-delete" type="danger" @click="handleDelete(scope.row)" round>删除</el-button>
</el-row>
</template>
</el-table-column>
</el-table>
<!-- 新增修改界面 -->
<el-dialog
:title="!dataForm.id ? '新增' : '修改'"
width="40%"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
>
<el-form
:model="dataForm"
:rules="dataRule"
ref="dataForm"
@keyup.enter.native="submitForm()"
label-width="80px"
:size="size"
style="text-align:left;"
>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="dataForm.type">
<el-radio v-for="(type, index) in menuTypeList" :label="index" :key="index">{{ type }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="menuTypeList[dataForm.type] + '名称'" prop="name">
<el-input v-model="dataForm.name" :placeholder="menuTypeList[dataForm.type] + '名称'"></el-input>
</el-form-item>
<el-form-item label="上级菜单" prop="parentName">
<PopupTreeInput
:data="popupTreeData"
:props="popupTreeProps"
:prop="dataForm.parentName==null?'根节点':dataForm.parentName"
:nodeKey="''+dataForm.parentId"
:currentChangeHandle="handleTreeSelectChange"
></PopupTreeInput>
</el-form-item>
<el-form-item v-if="dataForm.type === 1" label="菜单路由" prop="url">
<el-input v-model="dataForm.url" placeholder="菜单路由"></el-input>
</el-form-item>
<el-form-item v-if="dataForm.type !== 0" label="授权标识" prop="perms">
<el-input
v-model="dataForm.perms"
placeholder="如: sys:user:add, sys:user:edit, sys:user:delete"
></el-input>
</el-form-item>
<el-form-item v-if="dataForm.type !== 2" label="排序编号" prop="orderNum">
<el-input-number
v-model="dataForm.orderNum"
controls-position="right"
:min="0"
label="排序编号"
></el-input-number>
</el-form-item>
<el-form-item v-if="dataForm.type !== 2" label="菜单图标" prop="icon">
<el-input v-model="dataForm.icon" placeholder="请输入菜单图标"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button :size="size" @click="dialogVisible = false">取消</el-button>
<el-button :size="size" type="primary" @click="submitForm()">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import TableTreeColumn from "@/views/core/TableTreeColumn";
import PopupTreeInput from "@/components/PopupTreeInput";
export default {
components: { TableTreeColumn,PopupTreeInput},
data() {
return {
size: "small",
loading: false,
filters: {
name: ""
},
tableTreeDdata: [],
dialogVisible: false,
menuTypeList: ["目录", "菜单", "按钮"],
dataForm: {
id: 0,
type: 1,
name: "",
parentId: 0,
parentName: "",
url: "",
perms: "",
orderNum: 0,
icon: "",
iconList: []
},
dataRule: {
name: [
{ required: true, message: "菜单名称不能为空", trigger: "blur" }
],
parentName: [
{ required: true, message: "上级菜单不能为空", trigger: "change" }
]
},
popupTreeData: [],
popupTreeProps: {
label: "name",
children: "children"
}
};
},
methods: {
search: function() {},
// 获取数据
findTreeData: function() {
this.loading = true;
this.$api.menu.getMenu().then(res => {
this.tableTreeDdata = res.data;
this.popupTreeData = this.getParentMenuTree(res.data);
this.loading = false;
});
},
// 获取上级菜单树
getParentMenuTree: function(tableTreeDdata) {
let parent = {
parentId: -1,
name: "根节点",
children: tableTreeDdata
};
return [parent];
},
// 显示新增界面
handleAdd: function() {
this.dialogVisible = true;
this.dataForm = {
id: 0,
type: 1,
typeList: ["目录", "菜单", "按钮"],
name: "",
parentId: 0,
parentName: "",
url: "",
perms: "",
orderNum: 0,
icon: "",
iconList: []
};
},
// 显示编辑界面
handleEdit: function(row) {
this.dialogVisible = true;
Object.assign(this.dataForm, row);
},
// 删除
handleDelete(row) {
this.$confirm("确认删除选中记录吗?", "提示", {
type: "warning"
}).then(() => {
let params = this.getDeleteIds([], row);
this.$api.menu.batchDelete(params).then(res => {
this.findTreeData();
this.$message({ message: "删除成功", type: "success" });
});
});
},
// 获取删除的包含子菜单的id列表
getDeleteIds(ids, row) {
ids.push({ id: row.id });
if (row.children != null) {
for (let i = 0, len = row.children.length; i < len; i++) {
this.getDeleteIds(ids, row.children[i]);
}
}
return ids;
},
// 菜单树选中
handleTreeSelectChange(data, node) {
this.dataForm.parentId = data.id;
this.dataForm.parentName = data.name;
},
// 图标选中
iconActiveHandle(iconName) {
this.dataForm.icon = iconName;
},
// 表单提交
submitForm() {
this.$refs["dataForm"].validate(valid => {
if (valid) {
this.$confirm("确认提交吗?", "提示", {}).then(() => {
this.editLoading = true;
let params = Object.assign({}, this.dataForm);
this.$api.menu.save(params).then(res => {
if (res.code == 200) {
this.$message({ message: "操作成功", type: "success" });
} else {
this.$message({
message: "操作失败, " + res.msg,
type: "error"
});
}
this.editLoading = false;
this.$refs["dataForm"].resetFields();
this.dialogVisible = false;
this.findTreeData();
});
});
}
});
}
},
mounted() {
this.findTreeData();
}
};
</script>
<style scoped>
</style>