由于我的路由是自动加载的,所以菜单也是被vue文件或者文件夹控制,所以只要创建vue文件和文件夹就可以实现创建菜单。
第一步:修改自动加载路由vue-router
我之前的路由是文件夹下单个vue文件即是一级路由,而要变成二级路由就得要有两个vue文件以上,但是这样非常麻烦,还有就是一些页面不想在菜单中显示,但是又想使用,所以增加了一个hidde文件夹作为隐藏vue文件位置。
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/components/Layout'
import Login from '@/components/login'
import Index from '@/components/home/index'
import Menus from '@/assets/menu.json' //获取菜单中文名称图标等信息
Vue.use(Router)
let routers = require.context('@/components', true, /\.vue$/).keys();
var allname = []; //文件夹目录名称
var allRouters = []; //children嵌套路由数据
var autoLoadRoutes = []; //最终路由
var maxlen = 0;
//解决导航重复点击报错
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
//去除重复数组的对象
export function deteleObject(obj) {
var uniques = [];
var stringify = {};
for (var i = 0; i < obj.length; i++) {
var keys = Object.keys(obj[i]);
keys.sort(function(a, b) {
return (Number(a) - Number(b));
});
var str = '';
for (var j = 0; j < keys.length; j++) {
str += JSON.stringify(keys[j]);
str += JSON.stringify(obj[i][keys[j]]);
}
if (!stringify.hasOwnProperty(str)) {
uniques.push(obj[i]);
stringify[str] = true;
}
}
uniques = uniques;
return uniques;
}
//-------组合children路由-----------------------------------------------------
routers.forEach(r => {
var realr = r.replace(/^\.\//, '').replace(/\.\w+$/, '');
var name = realr.split("/");
var sname = name[0];
var cname = name[1];
if (name != "login" && name != "Layout") {
//获取子文件夹内vue文件加入数组
var objs = {
path: '/' + name[1],
name: name[1],
component: require("@/components" + r.substr(1)).default,
meta: {
title: Menus.apmenu[cname],
name: name[1],
comp: require("@/components" + r.substr(1)).default //选项卡tab获取路由路径
}
};
var key = name[0];
//根据文件夹名称重新组成children嵌套内容
if (Array.isArray(allRouters[key])) {
allRouters[key].push(objs);
} else {
allRouters[key] = [objs];
}
if (key != "home") {
allname.push({
filename: key
})
}
}
})
var allnames = deteleObject(allname) //去掉重复名称
//完成路由组合
for (var i = 0; i < allnames.length; i++) {
var name = allnames[i].filename;
if (name == "hidde") {
autoLoadRoutes.push({
path: "/" + name,
name: name,
hidden: true,
leaf: true, //二级路由
meta: {
title: Menus.apmenu[name],
icon: Menus.apicon[name],
requiresAuth: true //路由拦截
},
component: Layout,
children: allRouters[name]
})
} else {
autoLoadRoutes.push({
path: "/" + name,
name: name,
leaf: true, //二级路由
meta: {
title: Menus.apmenu[name],
icon: Menus.apicon[name],
requiresAuth: true //路由拦截
},
component: Layout,
children: allRouters[name]
})
}
}
//把autoLoadRoutes路由信息加入并注册路由
export default new Router({
mode: 'hash',
routes: [{
path: '/',
hidden: true, //不在导航显示
component: Layout,
redirect: '/index'
},
{
path: '/login',
name: 'Login',
hidden: true,
component: Login,
meta: {
title: '登录页',
name: 'Login',
comp: Login
}
},
{
//一级菜单
path: '/home',
component: Layout,
name: 'home',
redirect: '/index',
meta: {},
children: [{
path: '/index',
name: 'index',
component: Index,
meta: {
title: "首页",
name: 'index',
comp: Index, //tab获取路由地址
icon: "el-icon-s-platform",
requiresAuth: true //登录拦截
}
}]
},
...autoLoadRoutes
]
})
// export default router //注册路由
// console.log(router) //打印路由
以上为路由代码,如果想像首页那样单页面显示菜单,需要自己手动添加进去。
第二部:thinkphp服务端添加控制器
在G:\phpstudy_pro\WWW\Application\Home\Controller\下添加文件:MenuController.class.php
代码:
<?php
namespace Home\Controller;
use Think\Controller;
class MenuController extends Controller {
//创建子菜单
public function addmenus(){
// echo json_encode($_POST);
$name =$_POST['name'];
$cname =$_POST['cname'];
$file =$_POST['url'];
if(file_exists($file))
{
echo "101";//文件已存在
}
else
{
$json_string = file_get_contents("G:/electruon/Viapp/src/renderer/assets/menu.json");// 从文件中读取数据到PHP变量
$data = json_decode($json_string,true);// 把JSON字符串转成PHP数组
$data["apmenu"][$name] = $cname;
// echo json_encode($data,JSON_UNESCAPED_UNICODE);
$json_strings = json_encode($data,JSON_UNESCAPED_UNICODE);//JSON_UNESCAPED_UNICODE中文编码
file_put_contents("G:/electruon/Viapp/src/renderer/assets/menu.json",$json_strings);//写入
$myfile = fopen($_POST['url'], "w");//创建文件
fwrite($myfile,"<template> \r\n </template> \r\n");
fwrite($myfile,"<script> \r\n </script> \r\n");
fwrite($myfile,"<style> \r\n </style> \r\n");
$myfiler = fopen($_POST['url'], "r");//打开文件
while(!feof($myfiler)){
echo fgets($myfiler);
}
}
}
//创建一级菜单
public function addmenu(){
// echo json_encode($_POST);
// cname文件夹中文名字
// cnames文件中文名字
// icon图标
// name文件夹名称
// names文件名称
$cname =$_POST['cname'];
$cnames =$_POST['cnames'];
$icon =$_POST['icon'];
$name =$_POST['name'];
$names =$_POST['names'];
$fileurl = "G:/electruon/Viapp/src/renderer/components/";
$file = $fileurl.$name;
$files = $fileurl.$name."/".$names.".vue";
if(is_dir($file))
{
echo "101";//文件已存在
}
else{
echo "103";
$json_string = file_get_contents("G:/electruon/Viapp/src/renderer/assets/menu.json");// 从文件中读取数据到PHP变量
$data = json_decode($json_string,true);// 把JSON字符串转成PHP数组
$data["apmenu"][$name] = $cname;
$data["apmenu"][$names] = $cnames;
$data["apicon"][$name] = $icon;
$json_strings = json_encode($data,JSON_UNESCAPED_UNICODE);//JSON_UNESCAPED_UNICODE中文编码
// echo $json_strings;
file_put_contents("G:/electruon/Viapp/src/renderer/assets/menu.json",$json_strings);//写入
mkdir($file,0777);
$myfile = fopen($files, "w");//创建文件
fwrite($myfile,"<template> \r\n </template> \r\n");
fwrite($myfile,"<script> \r\n </script> \r\n");
fwrite($myfile,"<style> \r\n </style> \r\n");
$myfiler = fopen($files, "r");//打开文件
while(!feof($myfiler)){
echo fgets($myfiler);
}
}
}
}
以上代码是php处理代码,分别是创建文件夹和创建vue文件
第三步:制作菜单管理页面
在menu.vue文件中加入:
<template>
<div>
<!-- 表头 -->
<div style="color: #606266;margin-bottom: 10px;">
<span style="border:4px solid #5D78FF;border-radius:60px;vertical-align: middle;"></span>
<span style="font-size: 22px;margin-left: 10px;vertical-align: middle;font-weight: lighter;">菜单管理</span>
<span
style="font-size: 14px;margin-left: 10px;vertical-align: middle;font-weight: lighter;color: #909399;">**请确保当前系统是否空闲,菜单操作会导致所有应用刷新页面,所有未保存数据都将会丢失**</span>
</div>
<div style="border-bottom:1px solid #DCDFE6;margin-bottom: 15px;"></div>
<!-- ******* -->
<div style="margin-bottom: 10px;">
<el-button plain size="small" type="primary" @click="addmenus">新建一级菜单</el-button>
</div>
<div>
<el-table
:data="tabledata.filter(item => item.path !== '/' && item.path !== '/home' && item.path !== '/hidde' && item.path !== '/login')"
style="width: 100%;margin-bottom: 20px;" row-key="name" border default-expand-all
:tree-props="{children: 'children'}" :row-class-name="rowClass">
<el-table-column prop="name" label="Name" width="180px">
</el-table-column>
<el-table-column prop="path" label="Path" width="180px" align="center">
</el-table-column>
<el-table-column prop="meta.title" label="名称" width="120px" align="center">
</el-table-column>
<el-table-column prop="component.__file" label="Url" align="center">
</el-table-column>
<el-table-column label="操作" align="center" width="180px">
<template slot-scope="scope">
<el-button plain size="mini"
v-if="scope.row.component.__file != 'src/renderer/components/Layout.vue'">编辑</el-button>
<el-button plain size="mini"
v-if="scope.row.component.__file != 'src/renderer/components/Layout.vue'" type="danger"
@click="handledel(scope.$index, scope.row)">删除
</el-button>
<el-button plain size="mini"
v-if="scope.row.component.__file == 'src/renderer/components/Layout.vue'" type="success"
@click="handleadd(scope.$index, scope.row)">新增子菜单
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 创建一级菜单 -->
<el-dialog :visible.sync="newmenus" :append-to-body="true" :before-close="handleClose">
<!-- 表头 -->
<div style="color: #606266;margin-bottom: 10px;">
<span style="border:4px solid #5D78FF;border-radius:60px;vertical-align: middle;"></span>
<span
style="font-size: 22px;margin-left: 10px;vertical-align: middle;font-weight: lighter;">创建一级菜单</span>
</div>
<div style="border-bottom:1px solid #DCDFE6;margin-bottom: 15px;"></div>
<!-- ******* -->
<el-form ref="form" :model="menforms" label-width="100px">
<el-form-item label="菜单名称: ">
<el-input v-model="menforms.mennames"></el-input>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="中文名称: ">
<el-input v-model="menforms.mencnames"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="图标: ">
<el-row>
<el-col :span="14">
<el-input v-model="menforms.fileicon" :disabled="true"></el-input>
</el-col>
<el-col :span="4">
<el-popover placement="left" width="600" trigger="click">
<el-row>
<el-col v-for="(item,index) in cities" :key="index" :span="2" style="padding: 1px;">
<el-button size="mini" circle :icon="item" @click="selectIcon(item)">
</el-button>
</el-col>
</el-row>
<el-button style="margin-left: 5px;" size="mini" slot="reference" icon="el-icon-info"
circle></el-button>
</el-popover>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="子菜单名称: ">
<el-input v-model="menforms.filenames"></el-input>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="子中文名称: ">
<el-input v-model="menforms.filecnames"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item>
<el-button type="primary" style="width: 100%;" @click="addsetmenu">确定</el-button>
</el-form-item>
</el-form>
</el-dialog>
<!-- 创建子菜单 -->
<el-dialog :visible.sync="newmenu" :append-to-body="true" :before-close="handleClose">
<!-- 表头 -->
<div style="color: #606266;margin-bottom: 10px;">
<span style="border:4px solid #5D78FF;border-radius:60px;vertical-align: middle;"></span>
<span
style="font-size: 22px;margin-left: 10px;vertical-align: middle;font-weight: lighter;">创建子菜单</span>
</div>
<div style="border-bottom:1px solid #DCDFE6;margin-bottom: 15px;"></div>
<!-- ******* -->
<el-form ref="form" :model="menform" label-width="80px">
<el-form-item label="菜单名称: ">
<el-input v-model="menform.menname"></el-input>
</el-form-item>
<el-form-item label="中文名称: ">
<el-input v-model="menform.mencname"></el-input>
</el-form-item>
<el-form-item label="应用信息: ">
<span style="color: #909399;">{{menall}}</span>
</el-form-item>
<el-form-item>
<el-button type="primary" style="width: 100%;" @click="onaddmenu">确定</el-button>
</el-form-item>
</el-form>
</el-dialog>
<!-- 删除 -->
<el-dialog title="警告!" :visible.sync="delmenu" width="30%" :append-to-body="true">
<div style="color: #DD004E;">
<h2>删除容易,制作难</h2>
<h2>你确定要删除 <span style="font-size: 18px;color: #546CE6;">{{delname}}</span> 这个菜单吗?</h2>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="delmenu = false">取 消</el-button>
<el-button type="primary" @click="delmenukey">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import set from '../../../../set.json'
export default {
data() {
return {
tabledata: [],
newmenu: false, //子菜单
newmenus: false, //一级菜单
delmenu: false, //删除
menform: { //子菜单表单
menname: '',
mencname: ''
},
menforms: { //一级菜单表单
filenames: '',
filecnames: '',
mennames: '',
mencnames: '',
fileicon: ''
},
menall: '',
delname: '',
delkey: 0,
cities: ["el-icon-platform-eleme",
"el-icon-eleme",
"el-icon-delete-solid",
"el-icon-delete",
"el-icon-s-tools",
"el-icon-setting",
"el-icon-user-solid",
"el-icon-user",
"el-icon-phone",
"el-icon-phone-outline",
"el-icon-more",
"el-icon-more-outline",
"el-icon-star-on",
"el-icon-star-off",
"el-icon-s-goods",
"el-icon-goods",
"el-icon-warning",
"el-icon-warning-outline",
"el-icon-question",
"el-icon-info",
"el-icon-remove",
"el-icon-circle-plus",
"el-icon-success",
"el-icon-error",
"el-icon-zoom-in",
"el-icon-zoom-out",
"el-icon-remove-outline",
"el-icon-circle-plus-outline",
"el-icon-circle-check",
"el-icon-circle-close",
"el-icon-s-help",
"el-icon-help",
"el-icon-minus",
"el-icon-plus",
"el-icon-check",
"el-icon-close",
"el-icon-picture",
"el-icon-picture-outline",
"el-icon-picture-outline-round",
"el-icon-upload",
"el-icon-upload2",
"el-icon-download",
"el-icon-camera-solid",
"el-icon-camera",
"el-icon-video-camera-solid",
"el-icon-video-camera",
"el-icon-message-solid",
"el-icon-bell",
"el-icon-s-cooperation",
"el-icon-s-order",
"el-icon-s-platform",
"el-icon-s-fold",
"el-icon-s-unfold",
"el-icon-s-operation",
"el-icon-s-promotion",
"el-icon-s-home",
"el-icon-s-release",
"el-icon-s-ticket",
"el-icon-s-management",
"el-icon-s-open",
"el-icon-s-shop",
"el-icon-s-marketing",
"el-icon-s-flag",
"el-icon-s-comment",
"el-icon-s-finance",
"el-icon-s-claim",
"el-icon-s-custom",
"el-icon-s-opportunity",
"el-icon-s-data",
"el-icon-s-check",
"el-icon-s-grid",
"el-icon-menu",
"el-icon-share",
"el-icon-d-caret",
"el-icon-caret-left",
"el-icon-caret-right",
"el-icon-caret-bottom",
"el-icon-caret-top",
"el-icon-bottom-left",
"el-icon-bottom-right",
"el-icon-back",
"el-icon-right",
"el-icon-bottom",
"el-icon-top",
"el-icon-top-left",
"el-icon-top-right",
"el-icon-arrow-left",
"el-icon-arrow-right",
"el-icon-arrow-down",
"el-icon-arrow-up",
"el-icon-d-arrow-left",
"el-icon-d-arrow-right",
"el-icon-video-pause",
"el-icon-video-play",
"el-icon-refresh",
"el-icon-refresh-right",
"el-icon-refresh-left",
"el-icon-finished",
"el-icon-sort",
"el-icon-sort-up",
"el-icon-sort-down",
"el-icon-rank",
"el-icon-loading",
"el-icon-view",
"el-icon-c-scale-to-original",
"el-icon-date",
"el-icon-edit",
"el-icon-edit-outline",
"el-icon-folder",
"el-icon-folder-opened",
"el-icon-folder-add",
"el-icon-folder-remove",
"el-icon-folder-delete",
"el-icon-folder-checked",
"el-icon-tickets",
"el-icon-document-remove",
"el-icon-document-delete",
"el-icon-document-copy",
"el-icon-document-checked",
"el-icon-document",
"el-icon-document-add",
"el-icon-printer",
"el-icon-paperclip",
"el-icon-takeaway-box",
"el-icon-search",
"el-icon-monitor",
"el-icon-attract",
"el-icon-mobile",
"el-icon-scissors",
"el-icon-umbrella",
"el-icon-headset",
"el-icon-brush",
"el-icon-mouse",
"el-icon-coordinate",
"el-icon-magic-stick",
"el-icon-reading",
"el-icon-data-line",
"el-icon-data-board",
"el-icon-pie-chart",
"el-icon-data-analysis",
"el-icon-collection-tag",
"el-icon-film",
"el-icon-suitcase",
"el-icon-suitcase-1",
"el-icon-receiving",
"el-icon-collection",
"el-icon-files",
"el-icon-notebook-1",
"el-icon-notebook-2",
"el-icon-toilet-paper",
"el-icon-office-building",
"el-icon-school",
"el-icon-table-lamp",
"el-icon-house",
"el-icon-no-smoking",
"el-icon-smoking",
"el-icon-shopping-cart-full",
"el-icon-shopping-cart-1",
"el-icon-shopping-cart-2",
"el-icon-shopping-bag-1",
"el-icon-shopping-bag-2",
"el-icon-sold-out",
"el-icon-sell",
"el-icon-present",
"el-icon-box",
"el-icon-bank-card",
"el-icon-money",
"el-icon-coin",
"el-icon-wallet",
"el-icon-discount",
"el-icon-price-tag",
"el-icon-news",
"el-icon-guide",
"el-icon-male",
"el-icon-female",
"el-icon-thumb",
"el-icon-cpu",
"el-icon-link",
"el-icon-connection",
"el-icon-open",
"el-icon-turn-off",
"el-icon-set-up",
"el-icon-chat-round",
"el-icon-chat-line-round",
"el-icon-chat-square",
"el-icon-chat-dot-round",
"el-icon-chat-dot-square",
"el-icon-chat-line-square",
"el-icon-message",
"el-icon-postcard",
"el-icon-position",
"el-icon-turn-off-microphone",
"el-icon-microphone",
"el-icon-close-notification",
"el-icon-bangzhu",
"el-icon-time",
"el-icon-odometer",
"el-icon-crop",
"el-icon-aim",
"el-icon-switch-button",
"el-icon-full-screen",
"el-icon-copy-document",
"el-icon-mic",
"el-icon-stopwatch",
"el-icon-medal-1",
"el-icon-medal",
"el-icon-trophy",
"el-icon-trophy-1",
"el-icon-first-aid-kit",
"el-icon-discover",
"el-icon-place",
"el-icon-location",
"el-icon-location-outline",
"el-icon-location-information",
"el-icon-add-location",
"el-icon-delete-location",
"el-icon-map-location",
"el-icon-alarm-clock",
"el-icon-timer",
"el-icon-watch-1",
"el-icon-watch",
"el-icon-lock",
"el-icon-unlock",
"el-icon-key",
"el-icon-service",
"el-icon-mobile-phone",
"el-icon-bicycle",
"el-icon-truck",
"el-icon-ship",
"el-icon-basketball",
"el-icon-football",
"el-icon-soccer",
"el-icon-baseball",
"el-icon-wind-power",
"el-icon-light-rain",
"el-icon-lightning",
"el-icon-heavy-rain",
"el-icon-sunrise",
"el-icon-sunrise-1",
"el-icon-sunset",
"el-icon-sunny",
"el-icon-cloudy",
"el-icon-partly-cloudy",
"el-icon-cloudy-and-sunny",
"el-icon-moon",
"el-icon-moon-night",
"el-icon-dish",
"el-icon-dish-1",
"el-icon-food",
"el-icon-chicken",
"el-icon-fork-spoon",
"el-icon-knife-fork",
"el-icon-burger",
"el-icon-tableware",
"el-icon-sugar",
"el-icon-dessert",
"el-icon-ice-cream",
"el-icon-hot-water",
"el-icon-water-cup",
"el-icon-coffee-cup",
"el-icon-cold-drink",
"el-icon-goblet",
"el-icon-goblet-full",
"el-icon-goblet-square",
"el-icon-goblet-square-full",
"el-icon-refrigerator",
"el-icon-grape",
"el-icon-watermelon",
"el-icon-cherry",
"el-icon-apple",
"el-icon-pear",
"el-icon-orange",
"el-icon-coffee",
"el-icon-ice-tea",
"el-icon-ice-drink",
"el-icon-milk-tea",
"el-icon-potato-strips",
"el-icon-lollipop",
"el-icon-ice-cream-square",
"el-icon-ice-cream-round"
]
}
},
methods: {
//获取菜单列表
menuslist() {
this.tabledata = this.$router.options.routes
},
//表格增加子菜单按钮
handleadd(index, row) {
this.newmenu = true;
this.menall = set.url + "src/renderer/components" + row.path + "/"
},
//表格删除按钮
handledel(index, row) {
this.delmenu = true;
this.delname = row.name;
this.menall = set.url + "src/renderer/components" + row.path + "/"
},
//删除提交
delmenukey() {
this.delkey = this.delkey + 1;
if (this.delkey == 3) {
this.delkey = 0;
this.$message.warning({
message: '哈哈!你被骗了宝贝,这里并不能删除!'
})
}
},
//窗口防止错点提示
handleClose(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
},
rowClass({
row,
rowIndex
}) {
var opop = row.component.__file
if (opop === "src/renderer/components/Layout.vue") {
return 'warning-row';
}
return '';
},
//创建子菜单提交
onaddmenu() {
let menunameData = new FormData();
menunameData.append('name', this.menform.menname);
menunameData.append('cname', this.menform.mencname);
menunameData.append('url', this.menall + this.menform.menname + ".vue");
this.$http.post('http://127.0.0.1:8090/index.php/Home/Menu/addmenus', menunameData)
.then(res => {
console.log(res.data);
if (res.data == "101") {
this.$message.warning({
message: '文件已存在'
})
} else {
this.$message.warning({
message: '创建成功'
})
}
})
},
//创建一级菜单
addmenus() {
this.newmenus = true;
},
//获取图标
selectIcon(city) {
this.menforms.fileicon = city
},
//增加一级菜单提交
addsetmenu(){
if(this.menforms.mennames == "" || this.menforms.mencnames == "" || this.menforms.fileicon =="" || this.menforms.filenames == "" || this.menforms.filecnames == ""){
this.$message.warning({
message: '请填写完整信息'
})
}else{
let menuData = new FormData();
menuData.append('name', this.menforms.mennames);
menuData.append('cname', this.menforms.mencnames);
menuData.append('icon', this.menforms.fileicon);
menuData.append('names', this.menforms.filenames);
menuData.append('cnames', this.menforms.filecnames);
this.$http.post('http://127.0.0.1:8090/index.php/Home/Menu/addmenu', menuData)
.then(res => {
// console.log(res.data);
if (res.data == "101") {
this.$message.warning({
message: '文件已存在'
})
} else {
this.$message.warning({
message: '创建成功'
})
}
})
}
}
},
created() {
// console.log(this.$router.options.routes)
this.menuslist()
}
}
</script>
<style>
.conls {
text-align: center;
}
.el-table .warning-row {
background: #f5f7fa;
color: #000000;
}
.el-table__placeholder::before {
background: url('../../../../static/images/file.png') no-repeat 0 3px;
content: '';
display: block;
width: 16px;
height: 18px;
font-size: 16px;
background-size: 16px;
}
</style>
时间有限所以对话框和图标那部分没有封装,自己封装。
最终效果:
添加完成后需要在权限中开启权限,然后重新登录方可生效。
因为我之前开启过set的权限所以可以直接显示出来。