添加菜单分组配置

This commit is contained in:
icssoa 2022-06-07 22:40:21 +08:00
parent 55cbb1d7bb
commit ae58e804f9
15 changed files with 252 additions and 88 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "front-next", "name": "front-next",
"version": "5.2.4", "version": "5.3.0",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
"build": "vite build", "build": "vite build",
@ -9,7 +9,7 @@
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix" "lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix"
}, },
"dependencies": { "dependencies": {
"@cool-vue/crud": "^5.0.7", "@cool-vue/crud": "^5.0.10",
"@element-plus/icons-vue": "^1.1.3", "@element-plus/icons-vue": "^1.1.3",
"@vueuse/core": "^8.2.5", "@vueuse/core": "^8.2.5",
"axios": "^0.27.2", "axios": "^0.27.2",

View File

@ -12,6 +12,9 @@ export const config = {
// 菜单 // 菜单
menu: { menu: {
// 是否分组显示
isGroup: false,
// 自定义菜单列表
list: [] list: []
}, },
@ -25,16 +28,6 @@ export const config = {
views: [] views: []
}, },
// 主题
theme: {
// 主色
color: "",
// 样式地址
url: "",
// 显示一级菜单
showAMenu: false
},
// 字体图标库 // 字体图标库
iconfont: [] iconfont: []
}, },

View File

@ -2,17 +2,6 @@ import { config } from "/@/cool";
import { basename } from "/@/cool/utils"; import { basename } from "/@/cool/utils";
import { createLink } from "../utils"; import { createLink } from "../utils";
// 主题初始化
if (config.app.theme) {
const { url, color } = config.app.theme;
if (url) {
createLink(url, "theme-style");
}
document.getElementsByTagName("body")[0].style.setProperty("--color-primary", color);
}
// 字体图标库加载 // 字体图标库加载
if (config.app.iconfont) { if (config.app.iconfont) {
config.app.iconfont.forEach((e: string) => { config.app.iconfont.forEach((e: string) => {

View File

@ -0,0 +1,114 @@
<template>
<div class="a-menu">
<el-menu
:default-active="active"
mode="horizontal"
background-color="transparent"
@select="select"
>
<el-menu-item v-for="(item, index) in menu.group" :key="index" :index="`${index}`">
<icon-svg v-if="item.icon" :name="item.icon" :size="18" />
<span class="a-menu__name">{{ item.name }}</span>
</el-menu-item>
</el-menu>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { useBase } from "/$/base";
import { useCool } from "/@/cool";
const { router, route } = useCool();
const { menu } = useBase();
//
const active = ref<string>("0");
//
function select(index: number) {
menu.setMenu(index);
//
const url = menu.getPath(menu.group[index].children);
router.push(url);
}
onMounted(function () {
//
function deep(e: any, i: number) {
switch (e.type) {
case 0:
e.children.forEach((e: any) => {
deep(e, i);
});
break;
case 1:
if (route.path.includes(e.path)) {
active.value = String(i);
menu.setMenu(i);
}
break;
case 2:
default:
break;
}
}
menu.group.forEach((e: any, i: number) => {
deep(e, i);
});
});
</script>
<style lang="scss" scoped>
.a-menu {
margin: 5px 0 0 5px;
.el-menu {
height: 40px;
background: transparent;
border: 0;
:deep(.el-sub-menu__title) {
border: 0 !important;
}
:deep(.el-menu-item) {
display: flex;
align-items: center;
height: 40px;
padding: 0 15px;
background: transparent;
border: 0;
color: #999;
span {
font-size: 12px;
margin-left: 3px;
line-height: normal;
}
&:hover {
background: transparent;
}
&.is-active {
color: var(--color-primary);
border-radius: 5px 5px 0 0;
background: #fff;
color: #000;
}
.icon-svg {
margin-right: 5px;
}
}
}
&__name {
margin-left: 8px;
}
}
</style>

View File

@ -135,8 +135,9 @@ watch(
height: 30px; height: 30px;
padding: 0 10px; padding: 0 10px;
border-radius: 3px; border-radius: 3px;
cursor: pointer;
margin-right: 10px; margin-right: 10px;
font-size: 12px;
cursor: pointer;
&:hover { &:hover {
background-color: #eee; background-color: #eee;

View File

@ -64,7 +64,8 @@ export default defineComponent({
} }
}, },
{ {
immediate: true immediate: true,
deep: true
} }
); );

View File

@ -1,4 +1,6 @@
<template> <template>
<a-menu v-if="app.info.menu.isGroup" />
<div class="app-topbar"> <div class="app-topbar">
<div <div
class="app-topbar__collapse" class="app-topbar__collapse"
@ -51,6 +53,7 @@
import { useBase } from "/$/base"; import { useBase } from "/$/base";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import RouteNav from "./route-nav.vue"; import RouteNav from "./route-nav.vue";
import AMenu from "./amenu.vue";
const { router } = useCool(); const { router } = useCool();
const { user, app } = useBase(); const { user, app } = useBase();
@ -74,8 +77,8 @@ function onCommand(name: string) {
align-items: center; align-items: center;
height: 50px; height: 50px;
padding: 0 10px; padding: 0 10px;
margin-bottom: 10px;
background-color: #fff; background-color: #fff;
margin-bottom: 10px;
&__collapse { &__collapse {
display: flex; display: flex;

View File

@ -78,7 +78,7 @@ export default defineComponent({
const { user, menu } = useBase(); const { user, menu } = useBase();
// 1 // 1
const saving = ref<boolean>(false); const saving = ref(false);
// //
const form = reactive({ const form = reactive({
@ -88,27 +88,6 @@ export default defineComponent({
verifyCode: "" verifyCode: ""
}); });
//
function getPath(list: any[]) {
let path = "";
function deep(arr: any[]) {
arr.forEach((e: any) => {
if (e.type == 1) {
if (!path) {
path = e.path;
}
} else {
deep(e.children);
}
});
}
deep(list);
return path || "/";
}
// //
async function toLogin() { async function toLogin() {
if (!form.username) { if (!form.username) {
@ -135,7 +114,10 @@ export default defineComponent({
await user.get(); await user.get();
// //
const path = getPath(await menu.get()); await menu.get();
//
const path = menu.getPath();
if (path) { if (path) {
router.push(path); router.push(path);

View File

@ -51,13 +51,13 @@ export const useMenuStore = defineStore("menu", function () {
const perms = ref<any[]>(data["menu.perms"] || []); const perms = ref<any[]>(data["menu.perms"] || []);
// 设置左侧菜单 // 设置左侧菜单
function setMenu(i: number) { function setMenu(i?: number) {
if (isEmpty(index)) { if (i === undefined) {
i = index.value; i = index.value;
} }
// 显示一级菜单 // 显示分组显示菜单
if (config.app.theme.showAMenu) { if (config.app.menu.isGroup) {
const { children = [] } = group.value[i] || {}; const { children = [] } = group.value[i] || {};
index.value = i; index.value = i;
@ -105,12 +105,12 @@ export const useMenuStore = defineStore("menu", function () {
// 设置菜单组 // 设置菜单组
function setGroup(list: Item[]) { function setGroup(list: Item[]) {
group.value = orderBy(list, "orderNum"); group.value = orderBy(list, "orderNum").filter((e) => e.isShow);
storage.set("menu.group", group.value); storage.set("menu.group", group.value);
} }
// 获取菜单,权限信息 // 获取菜单,权限信息
function get(): Promise<any[]> { function get(): Promise<Item[]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
function next(res: any) { function next(res: any) {
if (!isArray(res.menus)) { if (!isArray(res.menus)) {
@ -174,6 +174,29 @@ export const useMenuStore = defineStore("menu", function () {
}); });
} }
// 获取菜单路径
function getPath(list?: Item[]) {
list = list || group.value;
let path = "";
function deep(arr: Item[]) {
arr.forEach((e: Item) => {
if (e.type == 1) {
if (!path) {
path = e.path;
}
} else {
deep(e.children || []);
}
});
}
deep(list);
return path || "/";
}
return { return {
routes, routes,
group, group,
@ -184,6 +207,7 @@ export const useMenuStore = defineStore("menu", function () {
setPerms, setPerms,
setMenu, setMenu,
setRoutes, setRoutes,
setGroup setGroup,
getPath
}; };
}); });

View File

@ -6,29 +6,35 @@
</div> </div>
<el-drawer v-model="visible" title="设置主题" size="350px" append-to-body> <el-drawer v-model="visible" title="设置主题" size="350px" append-to-body>
<el-form label-position="top"> <div class="cl-theme__drawer">
<el-form-item label="推荐"> <el-form label-position="top">
<ul class="cl-theme__comd"> <el-form-item label="推荐">
<li @click="setComd(item)" v-for="(item, name) in themes" :key="name"> <ul class="cl-theme__comd">
<div <li @click="setComd(item)" v-for="(item, name) in themes" :key="name">
class="w" <div
:style="{ class="w"
backgroundColor: item.color :style="{
}" backgroundColor: item.color
> }"
<check v-show="item.color == form.theme.color" /> >
</div> <check v-show="item.color == form.theme.color" />
</div>
<span>{{ item.label }}</span> <span>{{ item.label }}</span>
</li> </li>
</ul> </ul>
</el-form-item> </el-form-item>
<el-form-item label="自定义主色"> <el-form-item label="自定义主色">
<el-color-picker v-model="form.color" @change="setColor" /> <el-color-picker v-model="form.color" @change="setColor" />
<span class="ml-10px">{{ form.color }}</span> <span class="ml-10px">{{ form.color }}</span>
</el-form-item> </el-form-item>
</el-form>
<el-form-item label="菜单分组显示">
<el-switch v-model="form.theme.isGroup" @change="setGroup"></el-switch>
</el-form-item>
</el-form>
</div>
</el-drawer> </el-drawer>
</template> </template>
@ -39,6 +45,7 @@ import { module } from "/@/cool/utils";
import store from "store"; import store from "store";
import { Check } from "@element-plus/icons-vue"; import { Check } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useBase } from "/$/base";
export default defineComponent({ export default defineComponent({
name: "cl-theme", name: "cl-theme",
@ -48,9 +55,16 @@ export default defineComponent({
}, },
setup() { setup() {
const { app, menu } = useBase();
// //
const theme = reactive<any>(store.get("theme") || module.get("theme")); const theme = reactive<any>(store.get("theme") || module.get("theme"));
//
if (theme.isGroup === undefined) {
theme.isGroup = app.info.menu.isGroup;
}
// //
const form = reactive<any>({ const form = reactive<any>({
color: theme.color || "", color: theme.color || "",
@ -60,29 +74,46 @@ export default defineComponent({
// //
const visible = ref<boolean>(false); const visible = ref<boolean>(false);
//
function open() { function open() {
visible.value = true; visible.value = true;
} }
//
function setColor(color: string) { function setColor(color: string) {
setTheme({ color }); setTheme({ color });
} }
//
function setComd(item: any) { function setComd(item: any) {
form.theme = item; Object.assign(form.theme, item);
form.color = item.color; form.color = item.color;
setTheme(item); setTheme(item);
ElMessage.success(`切换主题:${item.label}`); ElMessage.success(`切换主题:${item.label}`);
} }
//
function setGroup(val: boolean) {
setTheme({ isGroup: val });
app.set({
menu: {
isGroup: val
}
});
menu.setMenu();
}
return { return {
app,
form, form,
themes, themes,
theme, theme,
visible, visible,
open, open,
setColor, setColor,
setComd setComd,
setTheme,
setGroup
}; };
} }
}); });
@ -129,5 +160,14 @@ export default defineComponent({
} }
} }
} }
&__drawer {
:deep(.el-form-item) {
background-color: #f7f7f7;
padding: 10px;
border-radius: 5px;
border: 1px solid #eee;
}
}
} }
</style> </style>

View File

@ -2,5 +2,5 @@ export default {
// 推荐主题:'jihei', 'guolv', 'jiangzi' // 推荐主题:'jihei', 'guolv', 'jiangzi'
name: "default" name: "default"
// 自定义主题色 // 自定义主题色
// color: "#4165d7" // color: "#4165d7",
}; };

View File

@ -1,6 +1,7 @@
import store from "store"; import store from "store";
import { App } from "vue"; import { App } from "vue";
import { setTheme } from "./utils"; import { setTheme } from "./utils";
import { config } from "/@/cool/config";
import "./static/css/index.scss"; import "./static/css/index.scss";
export default { export default {
@ -8,6 +9,10 @@ export default {
const theme = store.get("theme") || options; const theme = store.get("theme") || options;
if (theme) { if (theme) {
if (theme.isGroup !== undefined) {
config.app.menu.isGroup = theme.isGroup;
}
setTheme(theme); setTheme(theme);
} }
} }

View File

@ -3,6 +3,12 @@
.page-layout { .page-layout {
background-color: rgba(47, 52, 71, 0.9); background-color: rgba(47, 52, 71, 0.9);
.a-menu {
.el-menu-item {
border-radius: 5px !important;
}
}
.app-topbar { .app-topbar {
background-color: transparent; background-color: transparent;
color: #fff; color: #fff;

View File

@ -43,9 +43,10 @@ export const themes = [
declare interface Options { declare interface Options {
color?: string; color?: string;
name?: string; name?: string;
isGroup?: boolean;
} }
export function setTheme({ color, name }: Options) { export function setTheme({ color, name, isGroup }: Options) {
// 主题配置 // 主题配置
const theme = store.get("theme") || {}; const theme = store.get("theme") || {};
@ -69,6 +70,8 @@ export function setTheme({ color, name }: Options) {
color = item.color; color = item.color;
document.querySelector("#app")?.setAttribute("class", `theme-${name}`); document.querySelector("#app")?.setAttribute("class", `theme-${name}`);
} }
theme.name = name;
} }
// 设置主色 // 设置主色
@ -81,11 +84,14 @@ export function setTheme({ color, name }: Options) {
el.style.setProperty(`${pre}-light-${i}`, mix(color, mixWhite, i * 0.1)); el.style.setProperty(`${pre}-light-${i}`, mix(color, mixWhite, i * 0.1));
el.style.setProperty(`${pre}-dark-${i}`, mix(color, mixBlack, i * 0.1)); el.style.setProperty(`${pre}-dark-${i}`, mix(color, mixBlack, i * 0.1));
} }
theme.color = color;
} }
// 缓存 // 菜单分组显示
theme.name = name; if (isGroup !== undefined) {
theme.color = color; theme.isGroup = isGroup;
}
store.set("theme", theme); store.set("theme", theme);
} }

View File

@ -984,10 +984,10 @@
"@babel/helper-validator-identifier" "^7.16.7" "@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@cool-vue/crud@^5.0.7": "@cool-vue/crud@^5.0.10":
version "5.0.7" version "5.0.10"
resolved "https://registry.npmjs.org/@cool-vue/crud/-/crud-5.0.7.tgz#60459f3e58b04a08c621aa41e25c03618645bd6b" resolved "https://registry.npmjs.org/@cool-vue/crud/-/crud-5.0.10.tgz#c2d70504fccdf89c907e1d32e62a93dd777fba9c"
integrity sha512-/I4f6KFpPHiJE86w2pHAwu2Gn2WAd2LCFn6Bc3jzgg6RdYlAeIyRB9Ph8KasE7CCjyPdb2kyDYEQsZhOfODy9g== integrity sha512-a3jZPS+Y/+7IJTA3iYjD7PK83rwtIDrnE0tcf5LfenZ7JnnlXs+QFMRybGypJfoeswsG97JmWFeSY6o/JyALDw==
dependencies: dependencies:
array.prototype.flat "^1.2.4" array.prototype.flat "^1.2.4"
core-js "^3.21.1" core-js "^3.21.1"