Commit 50688879 authored by “yiyousong”'s avatar “yiyousong”

feat: 添加动态菜单

parent 5beea730
#开发环境 #开发环境
NODE_ENV = "development" NODE_ENV = "development"
VUE_APP_API_BASE_URL=http://192.168.0.98:11078 # VUE_APP_API_BASE_URL=http://192.168.0.98:11078
VUE_APP_API_BASE_URL=http://192.168.0.250:11078
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@ianwalter/vuex-reset": "^4.3.4", "@ianwalter/vuex-reset": "^4.3.4",
"@jiaminghi/data-view": "^2.10.0", "@jiaminghi/data-view": "^2.10.0",
"@riophae/vue-treeselect": "^0.4.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
......
...@@ -227,6 +227,15 @@ export const addRoleResource = (data) => { ...@@ -227,6 +227,15 @@ export const addRoleResource = (data) => {
}); });
}; };
// 添加角色菜单
export const addRoleMenu = (data) => {
return request({
url: `/bill/role/auth/distributionMenu`,
method: "post",
data,
});
};
/** /**
* 用户管理 * 用户管理
*/ */
...@@ -347,3 +356,12 @@ export const changeMenuSort = (data) => { ...@@ -347,3 +356,12 @@ export const changeMenuSort = (data) => {
data, data,
}); });
}; };
// 获取菜单树列表
export const getMenuTreeselect = (data) => {
return request({
url: `/bill/menu/treeselect`,
method: "post",
data,
});
};
<template>
<div class="flex h-full w-full flex-col bg-white">
<el-tabs :value="activeKey" @tab-click="changeRouter">
<el-tab-pane v-for="v in secondaryRoutes" :key="v.path" :name="v.path">
<template slot="label">
<i
v-if="v.meta.icon"
:class="['mr-[5px]', 'primary', v.meta.icon]"
></i>
<span class="tab-label">{{ v.meta.title }}</span>
</template>
</el-tab-pane>
</el-tabs>
<div class="flex-1 overflow-y-auto p-[15px]">
<router-view></router-view>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "TabMenuBar",
data() {
return {
subMenus: [],
};
},
computed: {
...mapGetters("user", ["secondaryRoutes"]),
activeKey() {
return this.$route.path;
},
},
created() {},
methods: {
changeRouter(e) {
this.$router.push(e.name);
},
},
};
</script>
<style lang="less" scoped></style>
...@@ -3,8 +3,8 @@ import store from "@/store"; ...@@ -3,8 +3,8 @@ import store from "@/store";
export const permission = { export const permission = {
inserted: function (el, binding) { inserted: function (el, binding) {
const { value } = binding; const { value } = binding;
const roles = store.getters["user/userId"]; const roles = store.getters["user/userInfo"].id;
if (value) { if (value && value instanceof Array && value.length > 0) {
const permissionRoles = value; const permissionRoles = value;
const hasPermission = permissionRoles.includes(roles); const hasPermission = permissionRoles.includes(roles);
if (!hasPermission) { if (!hasPermission) {
...@@ -15,3 +15,25 @@ export const permission = { ...@@ -15,3 +15,25 @@ export const permission = {
} }
}, },
}; };
// 按钮鉴权
export const hasPermi = {
inserted: function (el, binding) {
const { value } = binding;
const permissions = store.getters["user/permissions"];
if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value;
const hasPermissions = permissions.some((permission) => {
return permissionFlag.includes(permission);
});
if (!hasPermissions) {
el.remove();
}
} else {
throw new Error(`请设置操作权限标签值`);
}
},
};
...@@ -17,8 +17,7 @@ ...@@ -17,8 +17,7 @@
:default-active="activeMenu" :default-active="activeMenu"
mode="horizontal" mode="horizontal"
router router
text-color="rgba(254, 254, 254, 0.65)" @select="selectMenu"
background-color="#0000"
> >
<template v-for="v in menus"> <template v-for="v in menus">
<el-submenu <el-submenu
...@@ -55,7 +54,7 @@ ...@@ -55,7 +54,7 @@
<script> <script>
import HeaderSite from "./HeaderSite.vue"; import HeaderSite from "./HeaderSite.vue";
import { mapState } from "vuex"; import { mapState, mapActions } from "vuex";
export default { export default {
components: { components: {
HeaderSite, HeaderSite,
...@@ -75,20 +74,57 @@ export default { ...@@ -75,20 +74,57 @@ export default {
} }
return path; return path;
}, },
...mapState("user", ["menus", "sysName", "sysLogo", "path"]), ...mapState("user", ["menus", "sysName", "sysLogo", "path", "menus"]),
}, },
created() { created() {
document.title = this.sysName ? this.sysName : this.systemName; // 设置项目标题 document.title = this.sysName ? this.sysName : this.systemName; // 设置项目标题
}, },
methods: { methods: {
...mapActions("user", ["setSecondaryRoutes"]),
selectMenu(index) {
this.setSecondaryRoutes(index);
},
handleGoHome() { handleGoHome() {
this.$router.push("/home"); let path = this.menus[0].path;
this.$router.push(path);
}, },
}, },
}; };
</script> </script>
<style lang="less">
.el-menu--popup {
.el-menu-item {
display: flex;
align-items: center;
&:hover {
color: var(--primary) !important;
i {
color: var(--primary);
}
}
}
.is-active {
color: var(--primary) !important;
}
}
</style>
<style lang="less" scoped> <style lang="less" scoped>
@text-color: #fefefea5;
.mixins(@l,@t) {
content: "";
display: inline-block;
height: 4px;
width: 30%;
background: #ffffff;
border-radius: 2px;
position: absolute;
bottom: 0px;
left: @l;
transform: translateX(@t);
}
.header { .header {
height: 72px; height: 72px;
width: 100%; width: 100%;
...@@ -136,49 +172,67 @@ export default { ...@@ -136,49 +172,67 @@ export default {
:deep(.el-menu) { :deep(.el-menu) {
height: 100% !important; height: 100% !important;
border: none !important; border: none !important;
background: #0000;
.el-menu-item { .el-menu-item:not(.el-submenu) {
height: 100%; height: 100%;
font-size: 16px; font-size: 16px;
color: #fff;
display: flex; display: flex;
align-items: center; align-items: center;
border: none !important; border: none !important;
letter-spacing: 1px; letter-spacing: 1px;
color: @text-color;
i { i {
color: #fff; // color: #fff;
color: @text-color;
} }
&:hover { &:hover {
background: #0000 !important; background: #0000 !important;
color: @text-color !important;
&::after { &::after {
content: ""; .mixins(50%,-50%);
display: inline-block;
height: 4px;
width: 30%;
background: #ffffff;
border-radius: 2px;
position: absolute;
bottom: 0px;
left: 50%;
transform: translateX(-50%);
} }
} }
} }
.is-active { .el-submenu {
height: 100%;
font-size: 16px;
.el-submenu__title {
height: 100%;
display: flex;
font-size: 16px;
align-items: center;
border: none !important;
color: @text-color;
&:hover {
background: #0000 !important;
color: #fff !important;
&::after {
.mixins(calc(50% - 20px),-(50% - 20px));
}
}
}
.el-submenu__icon-arrow {
color: @text-color;
}
}
.is-active:not(.el-submenu),
.is-active .el-submenu__title {
color: #fff !important; color: #fff !important;
background: #0000 !important; background: #0000 !important;
font-weight: 600; font-weight: 600;
i {
color: #fff;
}
&::after {
.mixins(50%,-50%);
}
}
.is-active .el-submenu__title {
&::after { &::after {
content: ""; left: calc(50% - 20px);
display: inline-block; transform: translateX(-(50% - 20px));
height: 4px;
width: 30%;
background: #ffffff;
border-radius: 2px;
position: absolute;
bottom: 0px;
left: 50%;
transform: translateX(-50%);
} }
} }
} }
......
...@@ -4,19 +4,19 @@ ...@@ -4,19 +4,19 @@
<div class="tab-box flex items-end gap-5"> <div class="tab-box flex items-end gap-5">
<router-link <router-link
:class="['tab-item', 'top-radius']" :class="['tab-item', 'top-radius']"
to="/engine/queueupsystem" v-for="item in secondaryRoutes"
:key="item.path"
:to="item.path"
> >
排队取号系统 <i v-if="item.meta.icon" :class="['mr-2', item.meta.icon]"></i>
</router-link> {{ item.meta.title }}
<router-link
:class="['tab-item', 'top-radius']"
to="/engine/evaluatesystem"
>
评价系统
</router-link> </router-link>
</div> </div>
<div class="flex gap-5"> <div class="flex gap-5">
<div class="search-box top-radius flex h-full items-center gap-5"> <div
class="search-box top-radius flex h-full items-center gap-5"
v-hasPermi="['engine:query']"
>
<div class="text-[14px] text-[#395EBF]"> <div class="text-[14px] text-[#395EBF]">
统计时段:{{ time[0] }}~{{ time[1] }} 统计时段:{{ time[0] }}~{{ time[1] }}
</div> </div>
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
<div <div
class="engine-btn top-radius" class="engine-btn top-radius"
@click="$router.push('/enginesearch')" @click="$router.push('/enginesearch')"
v-hasPermi="['engine:enginesearch']"
> >
<img src="@/assets/img/engine.png" class="engine-img mr-2 w-[24px]" /> <img src="@/assets/img/engine.png" class="engine-img mr-2 w-[24px]" />
数据搜索引擎 数据搜索引擎
...@@ -48,6 +49,7 @@ ...@@ -48,6 +49,7 @@
</template> </template>
<script> <script>
import { mapGetters } from "vuex";
export default { export default {
data() { data() {
return { return {
...@@ -64,6 +66,9 @@ export default { ...@@ -64,6 +66,9 @@ export default {
}); });
}, },
}, },
computed: {
...mapGetters("user", ["secondaryRoutes"]),
},
created() { created() {
this.$nextTick(() => { this.$nextTick(() => {
this.handleDate(); this.handleDate();
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
:loading="hallLoading" :loading="hallLoading"
type="hall" type="hall"
@export="exportHallEva" @export="exportHallEva"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
...@@ -37,6 +38,8 @@ ...@@ -37,6 +38,8 @@
type="pjOption" type="pjOption"
@export="exportPjOptionEva" @export="exportPjOptionEva"
@search="handleSearch" @search="handleSearch"
hasPermiQuery="engine:queueupsystem:query"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
...@@ -61,6 +64,8 @@ ...@@ -61,6 +64,8 @@
type="dept" type="dept"
@export="exportDeptEva" @export="exportDeptEva"
@search="handleSearch" @search="handleSearch"
hasPermiQuery="engine:queueupsystem:query"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
...@@ -85,6 +90,8 @@ ...@@ -85,6 +90,8 @@
type="window" type="window"
@export="exportWindowEva" @export="exportWindowEva"
@search="handleSearch" @search="handleSearch"
hasPermiQuery="engine:queueupsystem:query"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
:loading="hallLoading" :loading="hallLoading"
type="hall" type="hall"
@export="exportHallQueue" @export="exportHallQueue"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
...@@ -37,6 +38,8 @@ ...@@ -37,6 +38,8 @@
type="business" type="business"
@export="exportbusinessQueue" @export="exportbusinessQueue"
@search="handleSearch" @search="handleSearch"
hasPermiQuery="engine:queueupsystem:query"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
...@@ -61,6 +64,8 @@ ...@@ -61,6 +64,8 @@
type="dept" type="dept"
@export="exportDeptQueue" @export="exportDeptQueue"
@search="handleSearch" @search="handleSearch"
hasPermiQuery="engine:queueupsystem:query"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
...@@ -85,6 +90,8 @@ ...@@ -85,6 +90,8 @@
type="window" type="window"
@export="exportWindowQueue" @export="exportWindowQueue"
@search="handleSearch" @search="handleSearch"
hasPermiQuery="engine:queueupsystem:query"
hasPermiExport="engine:queueupsystem:export"
></DoubleTable> ></DoubleTable>
</div> </div>
<Pagination <Pagination
......
...@@ -5,7 +5,11 @@ ...@@ -5,7 +5,11 @@
{{ title }} {{ title }}
</div> </div>
<div class="flex gap-4"> <div class="flex gap-4">
<el-button type="primary" size="small" @click="handleExport" <el-button
type="primary"
size="small"
@click="handleExport"
v-hasPermi="[hasPermiExport]"
>导出</el-button >导出</el-button
> >
...@@ -16,6 +20,7 @@ ...@@ -16,6 +20,7 @@
v-model="form.pjOption" v-model="form.pjOption"
placeholder="请选择评价选项" placeholder="请选择评价选项"
clearable clearable
v-hasPermi="[hasPermiQuery]"
> >
<el-option <el-option
v-for="(v, i) in dict.pjOption" v-for="(v, i) in dict.pjOption"
...@@ -32,6 +37,7 @@ ...@@ -32,6 +37,7 @@
v-model="form.businessName" v-model="form.businessName"
placeholder="请选择业务" placeholder="请选择业务"
clearable clearable
v-hasPermi="[hasPermiQuery]"
> >
<el-option <el-option
v-for="(v, i) in getTopKeyList(dict.businessList)" v-for="(v, i) in getTopKeyList(dict.businessList)"
...@@ -48,6 +54,7 @@ ...@@ -48,6 +54,7 @@
v-model="form.deptName" v-model="form.deptName"
placeholder="请选择部门" placeholder="请选择部门"
clearable clearable
v-hasPermi="[hasPermiQuery]"
> >
<el-option <el-option
v-for="(v, i) in getTopKeyList(dict.sectionNameList)" v-for="(v, i) in getTopKeyList(dict.sectionNameList)"
...@@ -64,6 +71,7 @@ ...@@ -64,6 +71,7 @@
v-model="form.windowNum" v-model="form.windowNum"
placeholder="请选择窗口" placeholder="请选择窗口"
clearable clearable
v-hasPermi="[hasPermiQuery]"
> >
<el-option <el-option
v-for="(v, i) in getTopKeyList(dict.windowFromnumList)" v-for="(v, i) in getTopKeyList(dict.windowFromnumList)"
...@@ -78,6 +86,7 @@ ...@@ -78,6 +86,7 @@
size="small" size="small"
v-if="type != 'hall'" v-if="type != 'hall'"
@click="handleSearch" @click="handleSearch"
v-hasPermi="[hasPermiQuery]"
>搜索</el-button >搜索</el-button
> >
</div> </div>
...@@ -153,6 +162,12 @@ export default { ...@@ -153,6 +162,12 @@ export default {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
hasPermiQuery: {
type: String,
},
hasPermiExport: {
type: String,
},
}, },
data() { data() {
return { return {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
size="small" size="small"
:loading="exportLoading" :loading="exportLoading"
@click="exportExcel" @click="exportExcel"
v-hasPermi="['market:evaluatereport:export']"
>导出</el-button >导出</el-button
> >
<div class="text-[#909399]"> <div class="text-[#909399]">
...@@ -17,7 +18,10 @@ ...@@ -17,7 +18,10 @@
统计时间段:{{ searchForm.time[0] }} ~ {{ searchForm.time[1] }} 统计时间段:{{ searchForm.time[0] }} ~ {{ searchForm.time[1] }}
</div> </div>
</div> </div>
<div class="flex items-center"> <div
class="flex items-center"
v-hasPermi="['market:evaluatereport:query']"
>
<el-form ref="searchForm" inline size="small" :model="searchForm"> <el-form ref="searchForm" inline size="small" :model="searchForm">
<el-form-item> <el-form-item>
<el-select style="width: 130px" v-model="searchForm.type"> <el-select style="width: 130px" v-model="searchForm.type">
...@@ -52,7 +56,11 @@ ...@@ -52,7 +56,11 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-popover placement="bottom" trigger="click"> <el-popover
placement="bottom"
trigger="click"
v-hasPermi="['market:evaluatereport:query']"
>
<div class="w-full"> <div class="w-full">
<el-form ref="searchForm2" inline size="small" :model="searchForm2"> <el-form ref="searchForm2" inline size="small" :model="searchForm2">
<el-form-item> <el-form-item>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
</div> </div>
<div class="flex h-full flex-1 flex-col bg-white"> <div class="flex h-full flex-1 flex-col bg-white">
<el-tabs :value="activeKey" @tab-click="changeRouter"> <el-tabs :value="activeKey" @tab-click="changeRouter">
<el-tab-pane v-for="v in subMenus" :key="v.path" :name="v.path"> <el-tab-pane v-for="v in secondaryRoutes" :key="v.path" :name="v.path">
<template slot="label"> <template slot="label">
<i <i
v-if="v.meta.icon" v-if="v.meta.icon"
...@@ -28,37 +28,25 @@ ...@@ -28,37 +28,25 @@
</template> </template>
<script> <script>
import { findBottomSubarrays } from "@/utils"; import { mapGetters } from "vuex";
export default { export default {
data() { data() {
return { return {
subMenus: [],
curTreeData: {}, // 当前选择的站点 curTreeData: {}, // 当前选择的站点
}; };
}, },
computed: { computed: {
...mapGetters("user", ["secondaryRoutes"]),
activeKey() { activeKey() {
return this.$route.path; return this.$route.path;
}, },
}, },
created() { created() {},
this.getSubMenus();
},
methods: { methods: {
changeRouter(e) { changeRouter(e) {
this.$router.push(e.name); this.$router.push(e.name);
}, },
// 获取当前顶层路由下的所有子路由
getSubMenus() {
let path = this.$route?.meta.activeMenu
? this.$route.meta.activeMenu
: this.$route.path;
let options = this.$router.options.routes[0].children;
let curRouters = options.filter((v) => v.path == path);
this.subMenus = findBottomSubarrays(curRouters).filter(
(v) => !v.meta.hidden
);
},
// 改变站点选择 // 改变站点选择
changeSite(data) { changeSite(data) {
this.curTreeData = data; this.curTreeData = data;
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
size="small" size="small"
:loading="exportLoading" :loading="exportLoading"
@click="exportExcel" @click="exportExcel"
v-hasPermi="['market:queueupreport:export']"
>导出</el-button >导出</el-button
> >
<div class="text-[#909399]"> <div class="text-[#909399]">
...@@ -17,7 +18,10 @@ ...@@ -17,7 +18,10 @@
统计时间段:{{ searchForm.time[0] }} ~ {{ searchForm.time[1] }} 统计时间段:{{ searchForm.time[0] }} ~ {{ searchForm.time[1] }}
</div> </div>
</div> </div>
<div class="flex items-center"> <div
class="flex items-center"
v-hasPermi="['market:queueupreport:query']"
>
<el-form ref="searchForm" inline size="small" :model="searchForm"> <el-form ref="searchForm" inline size="small" :model="searchForm">
<el-form-item> <el-form-item>
<el-select style="width: 130px" v-model="searchForm.type"> <el-select style="width: 130px" v-model="searchForm.type">
...@@ -52,7 +56,11 @@ ...@@ -52,7 +56,11 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<el-popover placement="bottom" trigger="click"> <el-popover
placement="bottom"
trigger="click"
v-hasPermi="['market:queueupreport:query']"
>
<div class="w-full"> <div class="w-full">
<el-form ref="searchForm2" inline size="small" :model="searchForm2"> <el-form ref="searchForm2" inline size="small" :model="searchForm2">
<!-- <el-form-item> <!-- <el-form-item>
......
...@@ -7,9 +7,14 @@ ...@@ -7,9 +7,14 @@
<script> <script>
import storage from "@/utils/storage"; import storage from "@/utils/storage";
import { mapMutations } from "vuex"; import { mapMutations } from "vuex";
import { getHomeData } from "@/api/home";
import { generateRoutes, filterBtn } from "@/utils";
import { calcMenu } from "@/router";
export default { export default {
data() { data() {
return {}; return {
menuList: [],
};
}, },
created() { created() {
this.getToken(); this.getToken();
...@@ -21,9 +26,12 @@ export default { ...@@ -21,9 +26,12 @@ export default {
"SET_sysLogo", "SET_sysLogo",
"SET_path", "SET_path",
"SET_userInfo", "SET_userInfo",
"SET_permissions",
"SET_routes",
"SET_menusList",
]), ]),
// 获取token // 获取token
getToken() { async getToken() {
let { token, userInfo, siteid, siteName, sysName, sysLogo, path } = let { token, userInfo, siteid, siteName, sysName, sysLogo, path } =
this.$route.query; this.$route.query;
if (token && userInfo) { if (token && userInfo) {
...@@ -35,7 +43,7 @@ export default { ...@@ -35,7 +43,7 @@ export default {
this.SET_path(path); this.SET_path(path);
storage.set(2, "siteId", siteid); storage.set(2, "siteId", siteid);
storage.set(2, "siteName", siteName); storage.set(2, "siteName", siteName);
this.$router.push("/home"); await this.getIndixData();
} else { } else {
this.$message.warning("跳转失败,请重新登录"); this.$message.warning("跳转失败,请重新登录");
setTimeout(() => { setTimeout(() => {
...@@ -43,6 +51,32 @@ export default { ...@@ -43,6 +51,32 @@ export default {
}, 2000); }, 2000);
} }
}, },
// 获取菜单列表
async getIndixData() {
let res = await getHomeData();
if (res.data.code == 1) {
let { menuList } = res.data.data;
// 过滤掉按钮
let menus = filterBtn(menuList, false);
let routes = generateRoutes(menus);
// this.SET_routes(routes);
this.SET_menusList(menus);
this.setBtnPermissions(menuList);
calcMenu();
if (routes.length) {
let path = routes[0].path;
this.$router.push(path);
}
}
},
// 添加按钮权限字符
setBtnPermissions(menuList) {
let btnPermissions = filterBtn(menuList)
.filter((v) => v.perms)
.map((v) => v.perms);
this.SET_permissions(btnPermissions);
},
}, },
}; };
</script> </script>
......
<template> <template>
<div class="system flex flex-col bg-white"> <div class="system">
<el-tabs :value="activeKey" @tab-click="changeRouter"> <TabMenuBar></TabMenuBar>
<el-tab-pane v-for="v in subMenus" :key="v.path" :name="v.path">
<template slot="label">
<i
v-if="v.meta.icon"
:class="['mr-[5px]', 'primary', v.meta.icon]"
></i>
<span class="tab-label">{{ v.meta.title }}</span>
</template>
</el-tab-pane>
</el-tabs>
<div class="system-out-box flex-1">
<router-view></router-view>
</div>
</div> </div>
</template> </template>
<script> <script>
import { findBottomSubarrays } from "@/utils";
export default { export default {
data() { data() {
return { return {
subMenus: [], subMenus: [],
}; };
}, },
computed: { computed: {},
activeKey() { created() {},
return this.$route.path; methods: {},
},
},
created() {
this.getSubMenus();
},
methods: {
changeRouter(e) {
this.$router.push(e.name);
},
// 获取当前顶层路由下的所有子路由
getSubMenus() {
let path = this.$route?.meta.activeMenu
? this.$route.meta.activeMenu
: this.$route.path;
let options = this.$router.options.routes[0].children;
let curRouters = options.filter((v) => v.path == path);
this.subMenus = findBottomSubarrays(curRouters).filter(
(v) => !v.meta.hidden
);
},
},
}; };
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
// :deep(.el-tabs__nav-scroll) {
// padding-left: 15px;
// }
.system { .system {
width: 100%; width: 100%;
height: 100%; height: 100%;
.system-out-box {
padding: 15px;
overflow-y: auto;
}
} }
</style> </style>
<template> <template>
<div> <el-popover placement="bottom-start" trigger="click" width="100%">
<el-popover placement="bottom-start" trigger="click"> <el-tree
<el-tree :data="treeData"
:data="treeData" :props="defaultProps"
:props="defaultProps" node-key="id"
node-key="id" ref="tree"
ref="tree" highlight-current
highlight-current accordion
accordion :expand-on-click-node="false"
:expand-on-click-node="false" :current-node-key="value"
:current-node-key="value" @node-click="handleNodeClick"
@node-click="handleNodeClick" ></el-tree>
></el-tree> <el-input
<el-input slot="reference"
slot="reference" :value="value"
:value="value" placeholder="请选择区域"
placeholder="请选择区域" clearable
clearable @clear="handleClear"
@clear="handleClear" ></el-input>
></el-input> </el-popover>
</el-popover>
</div>
</template> </template>
<script> <script>
export default { export default {
name: "InputTree",
props: { props: {
treeData: { treeData: {
type: Array, type: Array,
...@@ -35,14 +34,18 @@ export default { ...@@ -35,14 +34,18 @@ export default {
value: { value: {
default: "", default: "",
}, },
defaultProps: {
type: Object,
default: () => {
return {
children: "children",
label: "areaName",
};
},
},
}, },
data() { data() {
return { return {};
defaultProps: {
children: "children",
label: "areaName",
},
};
}, },
methods: { methods: {
handleClear() { handleClear() {
......
...@@ -107,7 +107,7 @@ export default { ...@@ -107,7 +107,7 @@ export default {
{ {
label: "菜单名称", label: "菜单名称",
prop: "name", prop: "name",
align: "center", // align: "center",
}, },
{ {
label: "ID", label: "ID",
...@@ -242,7 +242,7 @@ export default { ...@@ -242,7 +242,7 @@ export default {
this.current -= 1; this.current -= 1;
this.getMenuList(); this.getMenuList();
} }
this.menuList = [...this.menuList, ...data]; this.menuList = [...this.menuList, ...buildTree(data)];
this.tableData = buildTree(data); this.tableData = buildTree(data);
this.total = total; this.total = total;
this.dict = dict; this.dict = dict;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
:title="title" :title="title"
:destroy-on-close="true" :destroy-on-close="true"
:visible.sync="Visible" :visible.sync="Visible"
width="610px" width="650px"
@close="handleClose" @close="handleClose"
:close-on-click-modal="false" :close-on-click-modal="false"
top="10vh" top="10vh"
...@@ -14,31 +14,44 @@ ...@@ -14,31 +14,44 @@
:model="form" :model="form"
:rules="rules" :rules="rules"
size="small" size="small"
label-width="100px" label-width="130px"
> >
<el-form-item label="父级菜单" prop="parentId">
<treeselect
v-model="form.parentId"
:options="menuList"
:normalizer="normalizer"
:show-count="true"
placeholder="选择父级菜单"
/>
</el-form-item>
<el-form-item label="菜单类型" prop="menuType">
<el-radio-group v-model="form.menuType">
<el-radio
v-for="(item, key) in dict.menuType"
:key="key"
:label="Number(key)"
>{{ item }}</el-radio
>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单名称" prop="name"> <el-form-item label="菜单名称" prop="name">
<el-input <el-input
placeholder="请输入系统名称" placeholder="请输入菜单名称"
v-model="form.name" v-model="form.name"
clearable clearable
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="父级菜单" prop="parentId"> <el-form-item label="菜单图标" prop="imgPath" v-if="form.menuType != 2">
<el-select <IconSelect v-model="form.imgPath"></IconSelect>
clearable
v-model="form.parentId"
placeholder="请选择父级菜单"
>
<el-option
v-for="item in menuList"
:key="item.id"
:label="item.name"
:value="item.id"
>
</el-option>
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="权限类型" prop="authType">
<el-form-item
label="权限类型"
prop="authType"
v-if="form.menuType != 2"
>
<el-select <el-select
clearable clearable
v-model="form.authType" v-model="form.authType"
...@@ -53,62 +66,135 @@ ...@@ -53,62 +66,135 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="访问地址" prop="url"> <el-form-item prop="url" v-if="form.menuType != 2">
<span slot="label">
<el-tooltip content="访问的路由地址,如:`system`" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
路由地址
</span>
<el-input <el-input
clearable clearable
placeholder="请输入菜单访问地址" placeholder="请输入菜单路由地址"
v-model="form.url" v-model="form.url"
></el-input> ></el-input>
</el-form-item> </el-form-item>
<el-form-item label="类型" prop="menuType"> <el-form-item prop="component" v-if="form.menuType != 2">
<el-radio-group v-model="form.menuType"> <span slot="label">
<el-radio <el-tooltip
v-for="(item, key) in dict.menuType" content="访问的组件路径,如:`system/menu/Menu`,默认在`pages`目录下"
:key="key" placement="top"
:label="Number(key)"
>{{ item }}</el-radio
> >
</el-radio-group> <i class="el-icon-question"></i>
</el-tooltip>
组件路径
</span>
<el-input
clearable
placeholder="请输入组件路径"
v-model="form.component"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item prop="activeDir" v-if="form.menuType != 2">
<el-radio-group v-model="form.status"> <span slot="label">
<el-radio <el-tooltip content="顶部菜单栏激活路由" placement="top">
v-for="(item, key) in dict.status" <i class="el-icon-question"></i>
:key="key" </el-tooltip>
:label="Number(key)" 激活菜单
>{{ item }}</el-radio </span>
> <el-input
</el-radio-group> clearable
placeholder="请输入激活菜单地址"
v-model="form.activeDir"
></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="perms" v-if="form.menuType == 2">
<el-form-item label="图标" prop="imgPath"> <span slot="label">
<IconSelect v-model="form.imgPath"></IconSelect> <el-tooltip
<!-- <div class="icon-box grid grid-cols-2 gap-1"> content="控制器中定义的权限字符,如:'system:menu:add'"
<div placement="top"
:class="[
'icon-item',
'cursor-pointer',
{ active: form.imgPath == '' },
]"
@click="form.imgPath = ''"
> >
不需要图标 <i class="el-icon-question"></i>
</div> </el-tooltip>
<div 权限字符
:class="[ </span>
'icon-item', <el-input
'cursor-pointer', clearable
{ active: form.imgPath == v }, placeholder="请输入权限字符"
]" v-model="form.perms"
v-for="(v, i) in iconJson" ></el-input>
:key="i"
@click="form.imgPath = v"
>
<i :class="v"></i>
<span class="ml-2">{{ v }}</span>
</div>
</div> -->
</el-form-item> </el-form-item>
<el-row>
<el-col :span="12" v-if="form.menuType != 2">
<el-form-item prop="visible">
<span slot="label">
<el-tooltip
content="控制路由和子路由是否显示在菜单栏"
placement="top"
>
<i class="el-icon-question"></i>
</el-tooltip>
显示状态
</span>
<el-radio-group v-model="form.visible">
<el-radio
v-for="(item, key) in dict.visible"
:key="key"
:label="Number(key)"
>{{ item }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 2">
<el-form-item prop="cache">
<span slot="label">
<el-tooltip
content="选择是则会被`keep-alive`缓存"
placement="top"
>
<i class="el-icon-question"></i>
</el-tooltip>
是否缓存
</span>
<el-radio-group v-model="form.cache">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" v-if="form.menuType != 2">
<el-form-item prop="hideChildrenInMenu">
<span slot="label">
<el-tooltip
content="强制菜单显示为Item而不是SubItem"
placement="top"
>
<i class="el-icon-question"></i>
</el-tooltip>
是否隐藏子菜单
</span>
<el-radio-group v-model="form.hideChildrenInMenu">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="(item, key) in dict.status"
:key="key"
:label="Number(key)"
>{{ item }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button size="small" @click="handleRest">重 置</el-button> <el-button size="small" @click="handleRest">重 置</el-button>
...@@ -124,9 +210,12 @@ ...@@ -124,9 +210,12 @@
import { saveMenu } from "@/api/system"; import { saveMenu } from "@/api/system";
import IconSelect from "./IconSelect.vue"; import IconSelect from "./IconSelect.vue";
import iconJson from "@/assets/icon.json"; import iconJson from "@/assets/icon.json";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default { export default {
components: { components: {
IconSelect, IconSelect,
Treeselect,
}, },
props: { props: {
title: { title: {
...@@ -153,20 +242,40 @@ export default { ...@@ -153,20 +242,40 @@ export default {
data() { data() {
return { return {
iconJson, iconJson,
defaultProps: {
children: "childList",
label: "name",
},
form: { form: {
name: "", name: "", // 菜单名称
parentId: 0, url: "", // 路由地址
menuType: 0, ancestors: "", // 当前激活根目录
status: 1, parentId: 0, // 父菜单ID,一级菜单的该字段值为-1
url: "", linkType: 0, // 链接方式 (0.普通,1.弹出,2.脚本)
authType: "", imgPath: "", // 主菜单图标,主菜单图标的css样式名
imgPath: "", buttonImgPath: "", // 按钮图标,按钮图标的css样式名
imgCommPath: "", // 常用菜单图标,常用菜单图标的css样式名
commMenu: 1, // 是否常用菜单 (0.非常用,1.常用)
component: "", // vue组件路径
menuType: 0, // 菜单类型 (0.目录,1.菜单,2.按钮)
authType: 0, // 权限类型 (0.无限制,1.无需登录查看,2.需要登录查看,3.需要角色权限查看)
visible: 0, // 菜单显示状态 (0.显示,1.隐藏)
perms: "", // 权限标识,多个逗号分割
remark: "", // 备注信息
orderId: "", // 排序编号
status: 1, // 菜单状态 (0.停用,1.启用)
cache: 0, // 是否缓存
activeDir: "", // 激活菜单
hideChildrenInMenu: 1, // 是否隐藏子菜单
}, },
rules: { rules: {
name: [{ required: true, message: "请输入菜单名称", trigger: "blur" }], name: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
url: [ url: [
{ required: true, message: "请输入菜单访问地址", trigger: "blur" }, { required: true, message: "请输入菜单访问地址", trigger: "blur" },
], ],
component: [
{ required: true, message: "请输入组件路径", trigger: "blur" },
],
authType: [ authType: [
{ required: true, message: "请输入选择权限类型", trigger: "change" }, { required: true, message: "请输入选择权限类型", trigger: "change" },
], ],
...@@ -218,6 +327,20 @@ export default { ...@@ -218,6 +327,20 @@ export default {
this.$resetForm("form"); this.$resetForm("form");
this.Visible = false; this.Visible = false;
}, },
/** 转换菜单数据结构 */
normalizer(node) {
if (node.childList && !node.childList.length) {
delete node.childList;
}
return {
id: node.id,
label: node.name,
children: node.childList,
};
},
changeParent(row) {
console.log(row);
},
}, },
}; };
</script> </script>
...@@ -230,7 +353,4 @@ export default { ...@@ -230,7 +353,4 @@ export default {
:deep(.el-select) { :deep(.el-select) {
width: 100%; width: 100%;
} }
.active {
color: var(--primary);
}
</style> </style>
...@@ -3,12 +3,6 @@ ...@@ -3,12 +3,6 @@
<div <div
class="content grid max-h-[300px] w-full grid-cols-2 gap-x-4 gap-y-2 overflow-auto" class="content grid max-h-[300px] w-full grid-cols-2 gap-x-4 gap-y-2 overflow-auto"
> >
<div
:class="['cursor-pointer indent-8', { active: value === '' }]"
@click="handleChange('')"
>
不需要图标
</div>
<div v-for="(v, i) in iconJson" :key="i"> <div v-for="(v, i) in iconJson" :key="i">
<div <div
:class="[ :class="[
...@@ -30,8 +24,8 @@ ...@@ -30,8 +24,8 @@
v-bind="$attrs" v-bind="$attrs"
v-on="$listeners" v-on="$listeners"
:value="value" :value="value"
readonly
:placeholder="placeholder" :placeholder="placeholder"
clearable
> >
<i v-if="value" slot="prefix" :class="value" /> <i v-if="value" slot="prefix" :class="value" />
<i v-else slot="prefix" class="el-icon-search" /> <i v-else slot="prefix" class="el-icon-search" />
......
...@@ -2,6 +2,7 @@ import Vue from "vue"; ...@@ -2,6 +2,7 @@ import Vue from "vue";
import VueRouter from "vue-router"; import VueRouter from "vue-router";
import Layouts from "@/layouts/Layouts.vue"; import Layouts from "@/layouts/Layouts.vue";
import store from "@/store"; import store from "@/store";
import { generateRoutes } from "@/utils";
// import local from "@/utils/local"; // import local from "@/utils/local";
// 解决重复点击同一个路由报错 // 解决重复点击同一个路由报错
const originalPush = VueRouter.prototype.push; const originalPush = VueRouter.prototype.push;
...@@ -9,62 +10,53 @@ VueRouter.prototype.push = function (location) { ...@@ -9,62 +10,53 @@ VueRouter.prototype.push = function (location) {
return originalPush.call(this, location).catch((err) => err); return originalPush.call(this, location).catch((err) => err);
}; };
Vue.use(VueRouter); Vue.use(VueRouter);
/**
* Route 对象配置:
* hidden:控制路由是否显示在在菜单
* hideChildrenInMenu:强制菜单显示为Item而不是SubItem
* meta对象配置:
* title:标题
* keepAlive:缓存该路由
* activeMenu:当前的激活路由
* icon:图标
* hidden:控制路由是否显示在在菜单
*/
const routes = [ const routes = [
{ {
path: "/", path: "/",
name: "/",
component: Layouts, component: Layouts,
redirect: "/sso", redirect: "/sso",
children: [ children: [
{ // {
path: "/home", // path: "/home",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
component: () => import("@/pages/home/Home"), // component: () => import("@/pages/home/Home"),
meta: { // meta: {
title: "数仓工作台", // title: "数仓工作台",
}, // },
}, // },
{ // {
path: "/engine", // path: "/engine",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
component: () => import("@/pages/engine/Engine"), // component: () => import("@/pages/engine/Engine"),
meta: { // meta: {
title: "数据引擎", // title: "数据引擎",
}, // },
redirect: "/engine/queueupsystem", // redirect: "/engine/queueupsystem",
children: [ // children: [
{ // {
path: "/engine/queueupsystem", // path: "/engine/queueupsystem",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
hidden: true, // hidden: true,
component: () => import("@/pages/engine/QueueUpSystem"), // component: () => import("@/pages/engine/QueueUpSystem"),
meta: { // meta: {
title: "排队取号系统", // title: "排队取号系统",
activeMenu: "/engine", // activeMenu: "/engine",
}, // },
}, // },
{ // {
path: "/engine/evaluatesystem", // path: "/engine/evaluatesystem",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
hidden: true, // hidden: true,
component: () => import("@/pages/engine/EvaluateSystem"), // component: () => import("@/pages/engine/EvaluateSystem"),
meta: { // meta: {
title: "评价系统", // title: "评价系统",
activeMenu: "/engine", // activeMenu: "/engine",
}, // },
}, // },
], // ],
}, // },
// 数据搜索引擎 // 数据搜索引擎
{ {
path: "/enginesearch", path: "/enginesearch",
...@@ -97,133 +89,133 @@ const routes = [ ...@@ -97,133 +89,133 @@ const routes = [
], ],
}, },
{ // {
path: "/market", // path: "/market",
name: "market", // name: "market",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
component: () => import("@/pages/market/Market"), // component: () => import("@/pages/market/Market"),
meta: { // meta: {
title: "数据集市", // title: "数据集市",
}, // },
redirect: "/market/queueupreport", // redirect: "/market/queueupreport",
children: [ // children: [
{ // {
path: "/market/queueupreport", // path: "/market/queueupreport",
name: "queueupreport", // name: "queueupreport",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
component: () => import("@/pages/market/QueueUpReport"), // component: () => import("@/pages/market/QueueUpReport"),
meta: { // meta: {
title: "排队数据报表", // title: "排队数据报表",
activeMenu: "/market", // activeMenu: "/market",
icon: "el-icon-guide", // icon: "el-icon-guide",
}, // },
}, // },
{ // {
path: "/market/evaluatereport", // path: "/market/evaluatereport",
name: "evaluatereport", // name: "evaluatereport",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
component: () => import("@/pages/market/EvaluateReport"), // component: () => import("@/pages/market/EvaluateReport"),
meta: { // meta: {
title: "评价数据报表", // title: "评价数据报表",
activeMenu: "/market", // activeMenu: "/market",
icon: "el-icon-collection", // icon: "el-icon-collection",
}, // },
}, // },
], // ],
}, // },
{ // {
path: "/system", // path: "/system",
hideChildrenInMenu: true, // hideChildrenInMenu: true,
component: () => import("@/pages/system/System"), // component: () => import("@/pages/system/System"),
meta: { // meta: {
title: "系统设置", // title: "系统设置",
}, // },
redirect: "/system/access", // redirect: "/system/access",
children: [ // children: [
{ // {
path: "/system/access", // path: "/system/access",
component: () => import("@/pages/system/access/Access.vue"), // component: () => import("@/pages/system/access/Access.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "区县接入", // title: "区县接入",
icon: "el-icon-set-up", // icon: "el-icon-set-up",
}, // },
}, // },
// { // // {
// path: "/system/areasystem", // // path: "/system/areasystem",
// component: () => import("@/pages/system/areaSystem/AreaSystem.vue"), // // component: () => import("@/pages/system/areaSystem/AreaSystem.vue"),
// meta: { // // meta: {
// activeMenu: "/system", // // activeMenu: "/system",
// title: "区县系统", // // title: "区县系统",
// icon: "el-icon-monitor", // // icon: "el-icon-monitor",
// }, // // },
// }, // // },
{ // {
path: "/system/user", // path: "/system/user",
component: () => import("@/pages/system/user/User.vue"), // component: () => import("@/pages/system/user/User.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "用户信息", // title: "用户信息",
icon: "el-icon-user", // icon: "el-icon-user",
}, // },
}, // },
{ // {
path: "/system/role", // path: "/system/role",
component: () => import("@/pages/system/role/Role.vue"), // component: () => import("@/pages/system/role/Role.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "角色信息", // title: "角色信息",
icon: "el-icon-postcard", // icon: "el-icon-postcard",
}, // },
}, // },
{ // {
path: "/system/resourcemanage", // path: "/system/resourcemanage",
component: () => // component: () =>
import("@/pages/system/resourceManage/ResourceManage.vue"), // import("@/pages/system/resourceManage/ResourceManage.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "资源信息", // title: "资源信息",
icon: "el-icon-box", // icon: "el-icon-box",
}, // },
}, // },
{ // {
path: "/system/menu", // path: "/system/menu",
component: () => import("@/pages/system/menu/Menu.vue"), // component: () => import("@/pages/system/menu/Menu.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "菜单管理", // title: "菜单管理",
icon: "el-icon-guide", // icon: "el-icon-guide",
}, // },
}, // },
{ // {
path: "/system/parameter", // path: "/system/parameter",
component: () => import("@/pages/system/parameter/Parameter.vue"), // component: () => import("@/pages/system/parameter/Parameter.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "系统参数", // title: "系统参数",
icon: "el-icon-cpu", // icon: "el-icon-cpu",
}, // },
}, // },
{ // {
path: "/system/task", // path: "/system/task",
component: () => import("@/pages/system/task/TaskSet.vue"), // component: () => import("@/pages/system/task/TaskSet.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "任务信息", // title: "任务信息",
icon: "el-icon-bank-card", // icon: "el-icon-bank-card",
}, // },
}, // },
{ // {
path: "/system/systemlogs", // path: "/system/systemlogs",
component: () => import("@/pages/system/systemlogs/SystemLogs.vue"), // component: () => import("@/pages/system/systemlogs/SystemLogs.vue"),
meta: { // meta: {
activeMenu: "/system", // activeMenu: "/system",
title: "系统日志", // title: "系统日志",
icon: "el-icon-notebook-1", // icon: "el-icon-notebook-1",
}, // },
}, // },
], // ],
}, // },
], ],
}, },
{ {
...@@ -277,10 +269,12 @@ function menusFilter(arr) { ...@@ -277,10 +269,12 @@ function menusFilter(arr) {
} }
// 动态菜单 // 动态菜单
export function calcMenu() { export function calcMenu() {
// dynamicRouter.forEach((v) => { let menusList = store.getters["user/menusList"];
// router.addRoute(v); let dynamicRouter = generateRoutes(menusList);
// }); dynamicRouter.forEach((v) => {
let menus = menusFilter(routes[0].children); router.addRoute("/", v);
});
let menus = menusFilter(dynamicRouter);
store.commit("user/SET_menus", menus); store.commit("user/SET_menus", menus);
} }
calcMenu(); calcMenu();
......
import { getSiteBusiness, getDepartment, getWindow } from "@/api/site"; import { getSiteBusiness, getDepartment, getWindow } from "@/api/site";
import { getHomeData } from "@/api/home"; import { getHomeData } from "@/api/home";
import { findInTree } from "@/utils";
export default { export default {
namespaced: true, namespaced: true,
state: { state: {
menus: [], // 菜单 menusList: [], // 原始菜单列表
menus: [], // 格式化菜单列表
routes: [], // 路由列表
secondaryRoutes: [], // 二级路由
barList: [], // 登录返回菜单 barList: [], // 登录返回菜单
homeData: {}, // 首页数据 homeData: {}, // 首页数据
token: "", token: "",
...@@ -16,6 +20,7 @@ export default { ...@@ -16,6 +20,7 @@ export default {
businessList: [], // 站点业务列表 businessList: [], // 站点业务列表
deptList: [], // 站点部门列表 deptList: [], // 站点部门列表
windowList: [], // 站点窗口列表 windowList: [], // 站点窗口列表
permissions: [], // 按钮权限字符列表
}, },
getters: { getters: {
siteId(state) { siteId(state) {
...@@ -37,8 +42,35 @@ export default { ...@@ -37,8 +42,35 @@ export default {
let { barList } = state.homeData; let { barList } = state.homeData;
return barList || []; return barList || [];
}, },
routes(state) {
return state.routes;
},
menus(state) {
return state.menus;
},
permissions(state) {
return state.permissions;
},
menusList(state) {
return state.menusList;
},
secondaryRoutes(state) {
return state.secondaryRoutes;
},
}, },
mutations: { mutations: {
SET_menusList(state, menusList) {
state.menusList = menusList;
},
SET_routes(state, routes) {
state.routes = routes;
},
SET_permissions(state, permissions) {
state.permissions = permissions;
},
SET_secondaryRoutes(state, secondaryRoutes) {
state.secondaryRoutes = secondaryRoutes;
},
SET_path(state, path) { SET_path(state, path) {
state.path = path; state.path = path;
}, },
...@@ -127,5 +159,11 @@ export default { ...@@ -127,5 +159,11 @@ export default {
context.commit("SET_windowList", data); context.commit("SET_windowList", data);
} }
}, },
// 设置二级路由
setSecondaryRoutes(context, value) {
let routes = findInTree(context.state.menus, "path", value);
let secondaryRoutes = routes.children || [];
context.commit("SET_secondaryRoutes", secondaryRoutes);
},
}, },
}; };
import Vue from "vue"; import Vue from "vue";
import CryptoJS from "crypto-js"; import CryptoJS from "crypto-js";
import moment from "moment"; import moment from "moment";
import { find, get } from "lodash-es";
// 加密数据 // 加密数据
export let encrypt = (str, keyStr, ivStr) => { export let encrypt = (str, keyStr, ivStr) => {
...@@ -205,3 +206,95 @@ export const getFieldFromArray = (arr, field, treeField) => { ...@@ -205,3 +206,95 @@ export const getFieldFromArray = (arr, field, treeField) => {
return result; return result;
}; };
// 生成路由
export const generateRoutes = (menuList) => {
let routers = menuList.map((item) => {
// 构造符合要求的结构
let path = item.url.charAt(0) === "/" ? item.url : "/" + item.url;
let name = item.url.charAt(0) === "/" ? item.url.slice(1) : item.url;
let activeMenu = item.activeDir
? item.activeDir.charAt(0) === "/"
? item.activeDir
: "/" + item.activeDir
: "";
let component = item.component
? item.component.charAt(0) === "/"
? item.component
: "/" + item.component
: null;
const newItem = {
path,
name,
hidden: !!item.visible,
hideChildrenInMenu: !!item.hideChildrenInMenu,
component: () => import(`@/pages${component}`),
meta: {
title: item.name,
icon: item.imgPath,
keepAlive: !!item.cache,
activeMenu,
},
};
// 递归处理子节点
if (item.children && item.children.length > 0) {
newItem.children = generateRoutes(item.children);
}
return newItem;
});
routers.forEach((v) => {
if (v.children && v.children.length) {
v.redirect = v.children[0].path;
}
});
return routers;
};
/**
* 过滤菜单按钮项并提取 menuType = 2 的项
* @param {Array} menuList - 菜单列表
* @param {Boolean} menuType - true 获取 menuType = 2 的项并返回为一维数组, false 剔除 menuType = 2 的项
* @param {Array} btnItems - 用于存储 menuType = 2 的项(递归使用)
* @returns {Array} - 返回提取到的 menuType = 2 的项(如果 menuType 为 true)或剔除 menuType = 2 的菜单
*/
export const filterBtn = (menuList, menuType = true, btnItems = []) => {
const result = menuList
.map((menu) => {
// 深拷贝对象,避免修改原数据
const newMenu = { ...menu };
if (newMenu.children && newMenu.children.length > 0) {
newMenu.children = filterBtn(newMenu.children, menuType, btnItems);
}
const btnItem = newMenu.menuType == 2;
if (menuType) {
if (btnItem) {
btnItems.push(newMenu);
}
return newMenu;
} else {
// 剔除 menuType = 2 的项
return !btnItem ? newMenu : null;
}
})
.filter((menu) => menu !== null); // 过滤掉 null 项
return menuType ? btnItems : result;
};
export function findInTree(data, key, value) {
// 递归遍历树形结构
function recursiveSearch(nodes) {
return find(nodes, (node) => {
if (get(node, key) === value) {
return true;
}
if (node.children) {
return recursiveSearch(node.children);
}
return false;
});
}
return recursiveSearch(data);
}
...@@ -989,6 +989,13 @@ ...@@ -989,6 +989,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.11" regenerator-runtime "^0.13.11"
"@babel/runtime@^7.3.1":
version "7.25.6"
resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2"
integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.22.5": "@babel/template@^7.22.5":
version "7.22.5" version "7.22.5"
resolved "https://registry.npmmirror.com/@babel/template/-/template-7.22.5.tgz" resolved "https://registry.npmmirror.com/@babel/template/-/template-7.22.5.tgz"
...@@ -1222,6 +1229,20 @@ ...@@ -1222,6 +1229,20 @@
resolved "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.21.tgz" resolved "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.21.tgz"
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
"@riophae/vue-treeselect@^0.4.0":
version "0.4.0"
resolved "https://registry.npmmirror.com/@riophae/vue-treeselect/-/vue-treeselect-0.4.0.tgz#0baed5a794cffc580b63591f35c125e51c0df241"
integrity sha512-J4atYmBqXQmiPFK/0B5sXKjtnGc21mBJEiyKIDZwk0Q9XuynVFX6IJ4EpaLmUgL5Tve7HAS7wkiGGSti6Uaxcg==
dependencies:
"@babel/runtime" "^7.3.1"
babel-helper-vue-jsx-merge-props "^2.0.3"
easings-css "^1.0.0"
fuzzysearch "^1.0.3"
is-promise "^2.1.0"
lodash "^4.0.0"
material-colors "^1.2.6"
watch-size "^2.0.0"
"@sideway/address@^4.1.3": "@sideway/address@^4.1.3":
version "4.1.4" version "4.1.4"
resolved "https://registry.npmmirror.com/@sideway/address/-/address-4.1.4.tgz" resolved "https://registry.npmmirror.com/@sideway/address/-/address-4.1.4.tgz"
...@@ -2140,7 +2161,7 @@ axios@^1.4.0: ...@@ -2140,7 +2161,7 @@ axios@^1.4.0:
form-data "^4.0.0" form-data "^4.0.0"
proxy-from-env "^1.1.0" proxy-from-env "^1.1.0"
babel-helper-vue-jsx-merge-props@^2.0.0: babel-helper-vue-jsx-merge-props@^2.0.0, babel-helper-vue-jsx-merge-props@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6" resolved "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg== integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg==
...@@ -3104,6 +3125,11 @@ duplexer@^0.1.2: ...@@ -3104,6 +3125,11 @@ duplexer@^0.1.2:
resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz" resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
easings-css@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/easings-css/-/easings-css-1.0.0.tgz#dde569003bb7a4a0c0b77878f5db3e0be5679c81"
integrity sha512-7Uq7NdazNfVtr0RNmPAys8it0zKCuaqxJStYKEl72D3j4gbvXhhaM7iWNbqhA4C94ygCye6VuyhzBRQC4szeBg==
easy-stack@1.0.1: easy-stack@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.npmmirror.com/easy-stack/-/easy-stack-1.0.1.tgz" resolved "https://registry.npmmirror.com/easy-stack/-/easy-stack-1.0.1.tgz"
...@@ -3684,6 +3710,11 @@ functional-red-black-tree@^1.0.1: ...@@ -3684,6 +3710,11 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" resolved "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz"
integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==
fuzzysearch@^1.0.3:
version "1.0.3"
resolved "https://registry.npmmirror.com/fuzzysearch/-/fuzzysearch-1.0.3.tgz#dffc80f6d6b04223f2226aa79dd194231096d008"
integrity sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==
gensync@^1.0.0-beta.2: gensync@^1.0.0-beta.2:
version "1.0.0-beta.2" version "1.0.0-beta.2"
resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz" resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz"
...@@ -4173,6 +4204,11 @@ is-plain-object@^2.0.4: ...@@ -4173,6 +4204,11 @@ is-plain-object@^2.0.4:
dependencies: dependencies:
isobject "^3.0.1" isobject "^3.0.1"
is-promise@^2.1.0:
version "2.2.2"
resolved "https://registry.npmmirror.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
is-stream@^1.1.0: is-stream@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz" resolved "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz"
...@@ -4485,7 +4521,7 @@ lodash.uniq@^4.5.0: ...@@ -4485,7 +4521,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz" resolved "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21: lodash@^4.0.0, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21" version "4.17.21"
resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz" resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
...@@ -4556,6 +4592,11 @@ make-dir@^3.0.2, make-dir@^3.1.0: ...@@ -4556,6 +4592,11 @@ make-dir@^3.0.2, make-dir@^3.1.0:
dependencies: dependencies:
semver "^6.0.0" semver "^6.0.0"
material-colors@^1.2.6:
version "1.2.6"
resolved "https://registry.npmmirror.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
mdn-data@2.0.14: mdn-data@2.0.14:
version "2.0.14" version "2.0.14"
resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz" resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz"
...@@ -5714,6 +5755,11 @@ regenerator-runtime@^0.13.11: ...@@ -5714,6 +5755,11 @@ regenerator-runtime@^0.13.11:
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
regenerator-transform@^0.15.1: regenerator-transform@^0.15.1:
version "0.15.1" version "0.15.1"
resolved "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz" resolved "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz"
...@@ -6688,6 +6734,11 @@ vuex@^3.6.2: ...@@ -6688,6 +6734,11 @@ vuex@^3.6.2:
resolved "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz" resolved "https://registry.npmmirror.com/vuex/-/vuex-3.6.2.tgz"
integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw== integrity sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==
watch-size@^2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/watch-size/-/watch-size-2.0.0.tgz#096ee28d0365bd7ea03d9c8bf1f2f50a73be1474"
integrity sha512-M92R89dNoTPWyCD+HuUEDdhaDnh9jxPGOwlDc0u51jAgmjUvzqaEMynXSr3BaWs+QdHYk4KzibPy1TFtjLmOZQ==
watchpack@^2.4.0: watchpack@^2.4.0:
version "2.4.0" version "2.4.0"
resolved "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.0.tgz" resolved "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.0.tgz"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment