v2.6.6版本发布 (#1784)

* 前端支持顶部菜单栏模式

* 自动化代码单条获取方法不再需要取data.xxx直接取用data

* 自动化代码支持选择索引

* 自动化代码回滚支持自行选择是否删除菜单、API、表

* 日志增加定期清理功能

* 权限配置API部分,支持根据API真实path进行筛选

* 修复了一些已知bug

* 修复了一个sql注入漏洞

---------

Co-authored-by: xuedinge <781408517@qq.com>
Co-authored-by: Qing Liang <106448173+xue-ding-e@users.noreply.github.com>
Co-authored-by: wall-js <wall_js@foxmail.com>
Co-authored-by: Wall <307606056@qq.com>
Co-authored-by: leooza <leooza@163.com>
Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: hxl <52553624@qq.com>
Co-authored-by: zsc1003 <11476303+zsc1003@user.noreply.gitee.com>
This commit is contained in:
PiexlMax(奇淼
2024-06-16 00:27:40 +08:00
committed by GitHub
co-authored by xuedinge Qing Liang wall-js Wall leooza piexlMax(奇淼 hxl zsc1003
parent b336529d42
commit 03e5bc515f
34 changed files with 874 additions and 552 deletions
@@ -2,7 +2,7 @@
<el-sub-menu
ref="subMenu"
:index="routerInfo.name"
class="gva-sub-menu dark:text-slate-300"
class="gva-sub-menu dark:text-slate-300 relative"
>
<template #title>
<div
@@ -52,7 +52,9 @@ const isCollapse = inject('isCollapse', {
default: false,
})
const sideHeight = computed(() => config.value.layout_side_item_height + 'px')
const sideHeight = computed(() => {
return config.value.layout_side_item_height + 'px'
})
</script>
@@ -29,6 +29,10 @@ const props = defineProps({
type: Object,
default: () => null,
},
mode :{
type : String,
default: "vertical"
}
})
@@ -36,7 +36,9 @@ defineProps({
},
})
const sideHeight = computed(() => config.value.layout_side_item_height + 'px')
const sideHeight = computed(() => {
return config.value.layout_side_item_height + 'px'
})
</script>
+91
View File
@@ -0,0 +1,91 @@
<template>
<div class="bg-white h-[calc(100%-4px)] text-slate-700 dark:text-slate-300 mx-2 dark:bg-slate-900 flex items-center w-full overflow-auto">
<el-menu
:default-active="active"
mode="horizontal"
class="border-r-0 w-[calc(100%-100px)] flex gap-1 items-center box-border h-[calc(100%-1px)]"
unique-opened
@select="selectMenuItem"
>
<template v-for="item in routerStore.asyncRouters[0].children">
<aside-component
v-if="!item.hidden"
:key="item.name"
:router-info="item"
mode="horizontal"
/>
</template>
</el-menu>
</div>
</template>
<script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue";
import { ref, provide, watchEffect, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
const appStore = useAppStore();
const { device, config } = storeToRefs(appStore);
defineOptions({
name: "GvaAside",
});
const route = useRoute();
const router = useRouter();
const routerStore = useRouterStore();
const isCollapse = ref(false);
const active = ref("");
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
watchEffect(() => {
if (device.value === "mobile") {
isCollapse.value = true;
} else {
isCollapse.value = false;
}
});
provide("isCollapse", isCollapse);
const selectMenuItem = (index, _, ele, aaa) => {
const query = {};
const params = {};
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
} else {
params[item.key] = item.value;
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
} else {
router.push({ name: index, query, params });
}
};
</script>
<style lang="scss" scoped>
.el-menu--horizontal.el-menu,
.el-menu--horizontal > .el-menu-item.is-active{
border-bottom : none !important;
}
.el-menu-item.is-active{
background-color: var(--el-color-primary-light-8) !important;
}
.dark{
.el-menu-item.is-active{
background-color: var(--el-color-primary-bg) !important;
}
}
</style>
+9 -110
View File
@@ -1,116 +1,15 @@
<template>
<div
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
:class="isCollapse ? '' : ' px-2'"
:style="{
width : layoutSideWidth + 'px',
}"
>
<el-scrollbar>
<transition
:duration="{ enter: 800, leave: 100 }"
mode="out-in"
name="el-fade-in-linear"
>
<el-menu
:collapse="isCollapse"
:collapse-transition="false"
:default-active="active"
class="border-r-0 w-full"
unique-opened
@select="selectMenuItem"
>
<template v-for="item in routerStore.asyncRouters[0].children">
<aside-component
v-if="!item.hidden"
:key="item.name"
:router-info="item"
/>
</template>
</el-menu>
</transition>
</el-scrollbar>
<div
class="absolute bottom-8 right-2 w-8 h-8 bg-gray-50 dark:bg-slate-800 flex items-center justify-center rounded cursor-pointer "
:class="isCollapse ? 'right-0 left-0 mx-auto' : 'right-2'"
@click="toggleCollapse"
>
<el-icon v-if="!isCollapse">
<DArrowLeft />
</el-icon>
<el-icon v-else>
<DArrowRight />
</el-icon>
</div>
<div>
<normal-mode v-if="config.side_mode != 'head' || (device === 'mobile'&& config.side_mode == 'head' )" />
<head-mode v-if="config.side_mode === 'head' && device !== 'mobile'" />
</div>
</template>
<script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue";
import { ref, provide, watchEffect, computed} from "vue";
import { useRoute, useRouter } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useAppStore } from "@/pinia"
import { storeToRefs } from "pinia"
const appStore = useAppStore()
const { device, config } = storeToRefs(appStore)
defineOptions({
name: "GvaAside",
});
const route = useRoute();
const router = useRouter();
const routerStore = useRouterStore();
const isCollapse = ref(false)
const active = ref("");
const layoutSideWidth = computed(() =>{
if (!isCollapse.value) {
return config.value.layout_side_width
}else{
return config.value.layout_side_collapsed_width
}
})
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
watchEffect(() =>{
if (device.value === 'mobile') {
isCollapse.value = true
}else{
isCollapse.value = false
}
})
provide("isCollapse", isCollapse);
const selectMenuItem = (index, _, ele, aaa) => {
const query = {};
const params = {};
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
} else {
params[item.key] = item.value;
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
} else {
router.push({ name: index, query, params });
}
};
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value;
};
import NormalMode from "./normalMode.vue"
import HeadMode from "./headMode.vue"
import { storeToRefs } from "pinia";
import { useAppStore } from "@/pinia";
const appStore = useAppStore();
const { config, device } = storeToRefs(appStore);
</script>
<style lang="scss">
</style>
+104
View File
@@ -0,0 +1,104 @@
<template>
<div
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
:class="isCollapse ? '' : ' px-2'"
:style="{
width: layoutSideWidth + 'px',
}"
>
<el-menu
:collapse="isCollapse"
:collapse-transition="false"
:default-active="active"
class="border-r-0 w-full"
unique-opened
@select="selectMenuItem"
>
<template v-for="item in routerStore.asyncRouters[0].children">
<aside-component
v-if="!item.hidden"
:key="item.name"
:router-info="item"
/>
</template>
</el-menu>
<div
class="absolute bottom-8 right-2 w-8 h-8 bg-gray-50 dark:bg-slate-800 flex items-center justify-center rounded cursor-pointer"
:class="isCollapse ? 'right-0 left-0 mx-auto' : 'right-2'"
@click="toggleCollapse"
>
<el-icon v-if="!isCollapse">
<DArrowLeft />
</el-icon>
<el-icon v-else>
<DArrowRight />
</el-icon>
</div>
</div>
</template>
<script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue";
import { ref, provide, watchEffect, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
const appStore = useAppStore();
const { device, config } = storeToRefs(appStore);
defineOptions({
name: "GvaAside",
});
const route = useRoute();
const router = useRouter();
const routerStore = useRouterStore();
const isCollapse = ref(false);
const active = ref("");
const layoutSideWidth = computed(() => {
if (!isCollapse.value) {
return config.value.layout_side_width;
} else {
return config.value.layout_side_collapsed_width;
}
});
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
watchEffect(() => {
if (device.value === "mobile") {
isCollapse.value = true;
} else {
isCollapse.value = false;
}
});
provide("isCollapse", isCollapse);
const selectMenuItem = (index, _, ele, aaa) => {
const query = {};
const params = {};
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
} else {
params[item.key] = item.value;
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
} else {
router.push({ name: index, query, params });
}
};
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value;
};
</script>
<style lang="scss"></style>
+6 -3
View File
@@ -17,12 +17,14 @@
<div
v-if="!isMobile"
class="inline-flex font-bold text-2xl ml-2"
:class="config.side_mode === 'head'&&'min-w-fit'"
>
{{ $GIN_VUE_ADMIN.appName }}
</div>
<gva-aside v-if="config.side_mode === 'head' && !isMobile" />
<el-breadcrumb v-show="!isMobile" class="ml-4">
<el-breadcrumb v-show="!isMobile" v-else class="ml-4">
<el-breadcrumb-item
v-for="item in matched.slice(1, matched.length)"
:key="item.path"
@@ -46,7 +48,7 @@
<CustomPic />
<span
v-show="!isMobile"
style="margin-left: 5px"
class="w-16"
>{{ userStore.userInfo.nickName }}</span>
<el-icon>
<arrow-down />
@@ -103,11 +105,12 @@ import { storeToRefs } from "pinia"
import { computed, ref } from 'vue'
import { setUserAuthority } from '@/api/user'
import { fmtTitle } from "@/utils/fmtRouterTitle";
import gvaAside from "@/view/layout/aside/index.vue"
const userStore = useUserStore();
const router = useRouter()
const route = useRoute()
const appStore = useAppStore()
const { device } = storeToRefs(appStore)
const { device , config } = storeToRefs(appStore)
const isMobile = computed(() =>{
return device.value === 'mobile'
})
+1 -1
View File
@@ -4,7 +4,7 @@
!-->
<template>
<div class="flex items-center mx-8 gap-4">
<div class="flex items-center mx-4 gap-4">
<el-tooltip
class=""
effect="dark"
+36 -27
View File
@@ -1,14 +1,29 @@
<template>
<div class="bg-gray-50 text-slate-700 dark:text-slate-500 dark:bg-slate-800 w-screen h-screen">
<el-watermark v-if="config.show_watermark" :font="font" :z-index="9999" :gap="[180,150]" class="absolute inset-0 pointer-events-none" :content="userStore.userInfo.nickName" />
<div
class="bg-gray-50 text-slate-700 dark:text-slate-500 dark:bg-slate-800 w-screen h-screen"
>
<el-watermark
v-if="config.show_watermark"
:font="font"
:z-index="9999"
:gap="[180, 150]"
class="absolute inset-0 pointer-events-none"
:content="userStore.userInfo.nickName"
/>
<gva-header />
<div class="flex flex-row w-full gva-container pt-16">
<gva-aside />
<div class="flex-1 w-0 h-full">
<div class="flex flex-row w-full gva-container pt-16">
<gva-aside v-if="config.side_mode != 'head' || (device === 'mobile'&& config.side_mode == 'head' )" />
<div class="flex-1 p-2 w-0 h-full">
<gva-tabs v-if="config.showTabs" />
<div class="overflow-auto" :class="config.showTabs? 'gva-container2' :'gva-container pt-1'">
<div
class="overflow-auto"
:class="config.showTabs ? 'gva-container2' : 'gva-container pt-1'"
>
<router-view v-if="reloadFlag" v-slot="{ Component }">
<div id="gva-base-load-dom" class="gva-body-h bg-gray-50 dark:bg-slate-800">
<div
id="gva-base-load-dom"
class="gva-body-h bg-gray-50 dark:bg-slate-800"
>
<transition mode="out-in" name="el-fade-in-linear">
<keep-alive :include="routerStore.keepAliveRouters">
<component :is="Component" />
@@ -26,32 +41,32 @@
<script setup>
import GvaAside from "@/view/layout/aside/index.vue";
import GvaHeader from "@/view/layout/header/index.vue";
import useResponsive from "@/hooks/responsive";
import GvaTabs from "./tabs/index.vue"
import useResponsive from "@/hooks/responsive";
import GvaTabs from "./tabs/index.vue";
import BottomInfo from "@/components/bottomInfo/bottomInfo.vue";
import { emitter } from "@/utils/bus.js";
import { ref, onMounted, nextTick, reactive, watchEffect} from "vue";
import { ref, onMounted, nextTick, reactive, watchEffect } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useUserStore } from "@/pinia/modules/user";
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
const appStore = useAppStore()
const { config, theme } = storeToRefs(appStore)
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
const appStore = useAppStore();
const { config, theme, device } = storeToRefs(appStore);
defineOptions({
name: "GvaLayout",
});
useResponsive(true)
useResponsive(true);
const font = reactive({
color: 'rgba(0, 0, 0, .15)',
})
color: "rgba(0, 0, 0, .15)",
});
watchEffect(()=>{
font.color = theme.value === 'dark' ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'
})
watchEffect(() => {
font.color =
theme.value === "dark" ? "rgba(255,255,255, .15)" : "rgba(0, 0, 0, .15)";
});
const router = useRouter();
const route = useRoute();
@@ -84,13 +99,7 @@ const reload = async () => {
}
}, 400);
};
</script>
<style lang="scss">
</style>
+117 -62
View File
@@ -1,40 +1,46 @@
<template>
<el-drawer
v-model="drawer"
title="系统配置"
direction="rtl"
:size="width"
>
<el-drawer v-model="drawer" title="系统配置" direction="rtl" :size="width">
<div class="flex flex-col">
<div class="mb-8">
<div class=" text-gray-800 dark:text-gray-100">
默认主题
</div>
<div class="text-gray-800 dark:text-gray-100">默认主题</div>
<div class="mt-2 text-sm p-2 flex items-center gap-2">
<el-segmented v-model="config.darkMode" :options="options" size="default" @change="appStore.toggleDarkMode" />
<el-segmented
v-model="config.darkMode"
:options="options"
size="default"
@change="appStore.toggleDarkMode"
/>
</div>
</div>
<div class="mb-8">
<div class=" text-gray-800 dark:text-gray-100">
主题色
</div>
<div class="text-gray-800 dark:text-gray-100">主题色</div>
<div class="mt-2 text-sm p-2 flex items-center gap-2">
<div v-for="item in colors" :key="item" class=" w-5 h-5 rounded cursor-pointer flex items-center justify-center" :style="`background:${item}`" @click="appStore.togglePrimaryColor(item)">
<div
v-for="item in colors"
:key="item"
class="w-5 h-5 rounded cursor-pointer flex items-center justify-center"
:style="`background:${item}`"
@click="appStore.togglePrimaryColor(item)"
>
<el-icon v-if="config.primaryColor === item">
<Select />
</el-icon>
</div>
<el-color-picker v-model="customColor" @change="appStore.togglePrimaryColor" />
<el-color-picker
v-model="customColor"
@change="appStore.togglePrimaryColor"
/>
</div>
</div>
<div class="mb-8">
<div class=" text-gray-800 dark:text-gray-100">
界面显示
</div>
<div class="text-gray-800 dark:text-gray-100">界面显示</div>
<div class="mt-2 text-sm p-2">
<div class="flex items-center justify-between">
<div>展示水印</div>
<el-switch v-model="config.show_watermark" @change="appStore.toggleConfigWatermark" />
<el-switch
v-model="config.show_watermark"
@change="appStore.toggleConfigWatermark"
/>
</div>
<div class="flex items-center justify-between">
<div>灰色模式</div>
@@ -42,95 +48,144 @@
</div>
<div class="flex items-center justify-between">
<div>色弱模式</div>
<el-switch v-model="config.weakness" @change="appStore.toggleWeakness" />
<el-switch
v-model="config.weakness"
@change="appStore.toggleWeakness"
/>
</div>
<div class="flex items-center justify-between">
<div>菜单模式</div>
<el-segmented
v-model="config.side_mode"
:options="sideModes"
size="default"
@change="appStore.toggleSideModel"
/>
<!-- <el-select
v-model="config.side_mode"
@change="handleSideModelChange"
>
<el-option value="normal" label="标准模式" />
<el-option value="head" label="顶部导航栏" />
<el-option value="multilayer" disabled label="多侧边导航模式" />
</el-select> -->
</div>
<div class="flex items-center justify-between">
<div>显示标签页</div>
<el-switch v-model="config.showTabs" @change="appStore.toggleTabs" />
<el-switch
v-model="config.showTabs"
@change="appStore.toggleTabs"
/>
</div>
</div>
</div>
<div class="mb-8">
<div class=" text-gray-800 dark:text-gray-100">
layout 大小配置
</div>
<div class="text-gray-800 dark:text-gray-100">layout 大小配置</div>
<div class="mt-2 text-sm p-2">
<div class="flex items-center justify-between mb-2">
<div>侧边栏展开宽度</div>
<el-input-number v-model="config.layout_side_width" :min="150" :max="400" :step="10"></el-input-number>
<el-input-number
v-model="config.layout_side_width"
:min="150"
:max="400"
:step="10"
></el-input-number>
</div>
<div class="flex items-center justify-between mb-2">
<div>侧边栏收缩宽度</div>
<el-input-number v-model="config.layout_side_collapsed_width" :min="60" :max="100"></el-input-number>
<el-input-number
v-model="config.layout_side_collapsed_width"
:min="60"
:max="100"
></el-input-number>
</div>
<div class="flex items-center justify-between mb-2">
<div>侧边栏子项高度</div>
<el-input-number v-model="config.layout_side_item_height" :min="30" :max="50"></el-input-number>
<el-input-number
v-model="config.layout_side_item_height"
:min="30"
:max="50"
></el-input-number>
</div>
</div>
</div>
<el-alert type="warning" :closable="false">
请注意,所有配置请保存到本地文件的 <el-tag>config.json</el-tag> 文件中,否则刷新页面后会丢失配置
请注意,所有配置请保存到本地文件的
<el-tag>config.json</el-tag> 文件中,否则刷新页面后会丢失配置
</el-alert>
<el-button type="primary" class="mt-4 " @click="copyConfig">复制配置json</el-button>
<el-button type="primary" class="mt-4" @click="copyConfig"
>复制配置json</el-button
>
</div>
</el-drawer>
</template>
<script setup>
import { useAppStore } from "@/pinia"
import { storeToRefs } from "pinia"
import { ref, computed } from "vue"
import { ElMessage } from 'element-plus'
const appStore = useAppStore()
const { config, device } = storeToRefs(appStore)
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
import { ref, computed } from "vue";
import { ElMessage } from "element-plus";
const appStore = useAppStore();
const { config, device } = storeToRefs(appStore);
defineOptions({
name: 'GvaSetting',
})
name: "GvaSetting",
});
const width = computed(() => {
return device.value === 'mobile' ? '100%' : '500px'
})
return device.value === "mobile" ? "100%" : "500px";
});
const colors = [
'#EB2F96',
'#3b82f6',
'#2FEB54',
'#EBEB2F',
'#EB2F2F',
'#2FEBEB',
]
"#EB2F96",
"#3b82f6",
"#2FEB54",
"#EBEB2F",
"#EB2F2F",
"#2FEBEB",
];
const drawer = defineModel('drawer',{
const drawer = defineModel("drawer", {
default: true,
type : Boolean
})
type: Boolean,
});
const options = ['dark','light','auto']
const options = ["dark", "light", "auto"];
const sideModes = [
{
label : "正常模式",
value : "normal"
},
{
label : "顶部菜单栏模式",
value: "head"
}
];
const copyConfig = () => {
const input = document.createElement('textarea')
input.value = JSON.stringify(config.value)
const input = document.createElement("textarea");
input.value = JSON.stringify(config.value);
// 添加回车
input.value = input.value.replace(/,/g, ',\n')
document.body.appendChild(input)
input.select()
document.execCommand('copy')
document.body.removeChild(input)
ElMessage.success('复制成功, 请自行保存到本地文件中')
}
input.value = input.value.replace(/,/g, ",\n");
document.body.appendChild(input);
input.select();
document.execCommand("copy");
document.body.removeChild(input);
ElMessage.success("复制成功, 请自行保存到本地文件中");
};
const customColor = ref("")
const customColor = ref("");
const handleSideModelChange = (e) => {
console.log(e);
};
</script>
<style lang="scss" scoped>
::v-deep(.el-drawer__header){
::v-deep(.el-drawer__header) {
@apply border-gray-400 dark:border-gray-600;
}
</style>