新版前端UI,预发布2.6.4版本。 (#1735)
* fix: 修改 layout 中相关 emit 和 重复声明的 theme ,采用最新的vue api * fix:修改 eslintrc 的配置文件,删除大多数无用配置 * fix: 修复 layout 切换手机样式模糊层的 bug * feature: 重构 layout , 添加暗黑模式 , 删除侧边栏颜色 * fix:细节调整,activeColor摘除。 * feature:右侧滚动模式调整 * feature:无用代码剔除 * fix: 修复暗黑模式相关细节 * fix: custome config layout * feature:样式细节调整 * fix: 增加前端样式配置文件 * feature:调整基础配置,增加表格边框 * feature:buttomInfo取消底色,a标签更改为活跃色 * feature:调整阴影颜色和配置同步 * feature:版本调整2.6.3==>2.6.4 --------- Co-authored-by: bypanghu <bypanghu@163.com>
This commit is contained in:
@@ -2,12 +2,15 @@
|
||||
<el-sub-menu
|
||||
ref="subMenu"
|
||||
:index="routerInfo.name"
|
||||
class="gva-sub-menu dark:text-slate-300"
|
||||
>
|
||||
|
||||
<template #title>
|
||||
<div
|
||||
v-if="!isCollapse"
|
||||
class="gva-subMenu"
|
||||
class="flex items-center "
|
||||
:style="{
|
||||
height : sideHeight,
|
||||
}"
|
||||
>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
@@ -26,75 +29,37 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { inject,computed } from 'vue'
|
||||
import { useAppStore } from '@/pinia'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const appStore = useAppStore()
|
||||
const { config } = storeToRefs(appStore)
|
||||
|
||||
defineOptions({
|
||||
name: 'AsyncSubmenu',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
routerInfo: {
|
||||
default: function() {
|
||||
return null
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const activeBackground = ref(props.theme.activeBackground)
|
||||
const activeText = ref(props.theme.activeText)
|
||||
const normalText = ref(props.theme.normalText)
|
||||
// const hoverBackground = ref(props.theme.hoverBackground)
|
||||
// const hoverText = ref(props.theme.hoverText)
|
||||
|
||||
watch(() => props.theme, () => {
|
||||
activeBackground.value = props.theme.activeBackground
|
||||
activeText.value = props.theme.activeText
|
||||
normalText.value = props.theme.normalText
|
||||
// hoverBackground.value = props.theme.hoverBackground
|
||||
// hoverText.value = props.theme.hoverText
|
||||
const isCollapse = inject('isCollapse', {
|
||||
default: false,
|
||||
})
|
||||
|
||||
const sideHeight = computed(() => config.value.layout_side_item_height + 'px')
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-sub-menu{
|
||||
::v-deep(.el-sub-menu__title){
|
||||
padding: 6px;
|
||||
color: v-bind(normalText);
|
||||
<style lang="scss">
|
||||
.gva-sub-menu{
|
||||
.el-sub-menu__title{
|
||||
height: v-bind('sideHeight');
|
||||
}
|
||||
}
|
||||
|
||||
.is-active:not(.is-opened){
|
||||
::v-deep(.el-sub-menu__title) .gva-subMenu{
|
||||
flex:1;
|
||||
height: 100%;
|
||||
line-height: 44px;
|
||||
background: v-bind(activeBackground) !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(activeBackground) !important;
|
||||
i{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
span{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gva-subMenu {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,17 +2,13 @@
|
||||
<component
|
||||
:is="menuComponent"
|
||||
v-if="!routerInfo.hidden"
|
||||
:is-collapse="isCollapse"
|
||||
:theme="theme"
|
||||
:router-info="routerInfo"
|
||||
>
|
||||
<template v-if="routerInfo.children&&routerInfo.children.length">
|
||||
<AsideComponent
|
||||
v-for="item in routerInfo.children"
|
||||
:key="item.name"
|
||||
:is-collapse="false"
|
||||
:router-info="item"
|
||||
:theme="theme"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
@@ -33,20 +29,9 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const menuComponent = computed(() => {
|
||||
if (props.routerInfo.children && props.routerInfo.children.filter(item => !item.hidden).length) {
|
||||
return AsyncSubmenu
|
||||
|
||||
@@ -1,120 +1,45 @@
|
||||
<template>
|
||||
<el-menu-item :index="routerInfo.name">
|
||||
<template v-if="isCollapse">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
:content="routerInfo.meta.title"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="gva-menu-item">
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<span class="gva-menu-item-title">{{ routerInfo.meta.title }}</span>
|
||||
</div>
|
||||
<el-menu-item
|
||||
:index="routerInfo.name"
|
||||
class="dark:text-slate-300"
|
||||
:style="{
|
||||
height : sideHeight,
|
||||
}"
|
||||
>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<template #title>
|
||||
{{ routerInfo.meta.title }}
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { computed, inject } from 'vue'
|
||||
import { useAppStore } from '@/pinia'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const appStore = useAppStore()
|
||||
const { config } = storeToRefs(appStore)
|
||||
|
||||
defineOptions({
|
||||
name: 'MenuItem',
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
routerInfo: {
|
||||
default: function() {
|
||||
return null
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const activeBackground = ref(props.theme.activeBackground)
|
||||
const activeText = ref(props.theme.activeText)
|
||||
const normalText = ref(props.theme.normalText)
|
||||
const hoverBackground = ref(props.theme.hoverBackground)
|
||||
const hoverText = ref(props.theme.hoverText)
|
||||
|
||||
watch(() => props.theme, () => {
|
||||
activeBackground.value = props.theme.activeBackground
|
||||
activeText.value = props.theme.activeText
|
||||
normalText.value = props.theme.normalText
|
||||
hoverBackground.value = props.theme.hoverBackground
|
||||
hoverText.value = props.theme.hoverText
|
||||
})
|
||||
const sideHeight = computed(() => config.value.layout_side_item_height + 'px')
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.gva-menu-item{
|
||||
width: 100%;
|
||||
flex:1;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding-left: 4px;
|
||||
|
||||
.gva-menu-item-title {
|
||||
flex:1;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--collapse{
|
||||
.el-menu-item.is-active{
|
||||
color: v-bind(activeBackground);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item{
|
||||
color: v-bind(normalText);
|
||||
}
|
||||
|
||||
.el-menu-item.is-active{
|
||||
.gva-menu-item{
|
||||
background: v-bind(activeBackground) !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(activeBackground) !important;
|
||||
i{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
span{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item:hover{
|
||||
.gva-menu-item{
|
||||
background: v-bind(hoverBackground);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(hoverBackground);
|
||||
color: v-bind(hoverText);
|
||||
}
|
||||
}
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,149 +1,114 @@
|
||||
<template>
|
||||
<div :style="{ background: userStore.sideMode }">
|
||||
<el-scrollbar style="height: calc(100vh - 60px)">
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 100 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
<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',
|
||||
}"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<el-menu
|
||||
:collapse="isCollapse"
|
||||
:collapse-transition="false"
|
||||
:default-active="active"
|
||||
:background-color="theme.background"
|
||||
:active-text-color="theme.active"
|
||||
class="el-menu-vertical"
|
||||
unique-opened
|
||||
@select="selectMenuItem"
|
||||
>
|
||||
<template v-for="item in routerStore.asyncRouters[0].children">
|
||||
<aside-component
|
||||
v-if="!item.hidden"
|
||||
:key="item.name"
|
||||
:is-collapse="isCollapse"
|
||||
:router-info="item"
|
||||
:theme="theme"
|
||||
/>
|
||||
</template>
|
||||
</el-menu>
|
||||
</transition>
|
||||
</el-scrollbar>
|
||||
<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>
|
||||
<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 { emitter } from '@/utils/bus.js'
|
||||
import { ref, watch, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
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: 'Aside',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const routerStore = useRouterStore()
|
||||
|
||||
const theme = ref({})
|
||||
|
||||
const getTheme = () => {
|
||||
switch (userStore.sideMode) {
|
||||
case '#fff':
|
||||
theme.value = {
|
||||
background: '#fff',
|
||||
activeBackground: 'var(--el-color-primary)',
|
||||
activeText: '#fff',
|
||||
normalText: '#333',
|
||||
hoverBackground: 'rgba(64, 158, 255, 0.08)',
|
||||
hoverText: '#333',
|
||||
}
|
||||
break
|
||||
case '#191a23':
|
||||
theme.value = {
|
||||
background: '#191a23',
|
||||
activeBackground: 'var(--el-color-primary)',
|
||||
activeText: '#fff',
|
||||
normalText: '#fff',
|
||||
hoverBackground: 'rgba(64, 158, 255, 0.08)',
|
||||
hoverText: '#fff',
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
getTheme()
|
||||
|
||||
const active = ref('')
|
||||
watch(() => route, () => {
|
||||
active.value = route.meta.activeName || route.name
|
||||
}, { deep: true })
|
||||
|
||||
watch(() => userStore.sideMode, () => {
|
||||
getTheme()
|
||||
})
|
||||
|
||||
name: "GvaAside",
|
||||
});
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const routerStore = useRouterStore();
|
||||
const isCollapse = ref(false)
|
||||
const initPage = () => {
|
||||
active.value = route.meta.activeName || route.name
|
||||
const screenWidth = document.body.clientWidth
|
||||
if (screenWidth < 1000) {
|
||||
isCollapse.value = !isCollapse.value
|
||||
const active = ref("");
|
||||
const layoutSideWidth = computed(() =>{
|
||||
if (!isCollapse.value) {
|
||||
return config.value.layout_side_width
|
||||
}else{
|
||||
return config.value.layout_side_collapsed_width
|
||||
}
|
||||
|
||||
emitter.on('collapse', (item) => {
|
||||
isCollapse.value = item
|
||||
})
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off('collapse')
|
||||
})
|
||||
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 = {}
|
||||
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)
|
||||
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 })
|
||||
router.push({ name: index, query, params });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleCollapse = () => {
|
||||
isCollapse.value = !isCollapse.value;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
.el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.menu-info {
|
||||
.menu-contorl {
|
||||
line-height: 52px;
|
||||
font-size: 20px;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
<!-- 此文件禁止修改!如果您没有购买授权,请联系wx:shouzi_1994购买授权,未授权状态只需保留此代码 不影响任何正常使用 -->
|
||||
|
||||
<template>
|
||||
<div class="flex gap-4 items-center text-sm text-gray-500 justify-center mb-4">
|
||||
<span>Powered by</span>
|
||||
<span>
|
||||
<a
|
||||
class="text-gray-800 font-bold"
|
||||
href="https://github.com/flipped-aurora/gin-vue-admin"
|
||||
>Gin-Vue-Admin</a>
|
||||
</span>
|
||||
<div class="flex flex-col md:flex-row gap-2 items-center text-sm text-slate-700 dark:text-slate-500 justify-center py-2">
|
||||
<div class="text-center">
|
||||
<span class="mr-1">Powered by</span>
|
||||
<span>
|
||||
<a
|
||||
class="font-bold text-active"
|
||||
href="https://github.com/flipped-aurora/gin-vue-admin"
|
||||
>Gin-Vue-Admin</a>
|
||||
</span>
|
||||
</div>
|
||||
<slot />
|
||||
<span>Copyright</span>
|
||||
<span>
|
||||
<a
|
||||
class="text-gray-800 font-bold"
|
||||
href="https://github.com/flipped-aurora"
|
||||
>flipped-aurora团队</a>
|
||||
</span>
|
||||
<div class="text-center">
|
||||
<span class="mr-1">Copyright</span>
|
||||
<span>
|
||||
<a
|
||||
class="font-bold text-active"
|
||||
href="https://github.com/flipped-aurora"
|
||||
>flipped-aurora团队</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<!--
|
||||
@auther: bypanghu<bypanghu@163.com>
|
||||
@date: 2024/5/7
|
||||
!-->
|
||||
|
||||
<template>
|
||||
<div class="fixed top-0 left-0 right-0 z-10 h-16 bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 flex items-center px-2">
|
||||
<div
|
||||
class="flex items-center "
|
||||
>
|
||||
<img
|
||||
alt
|
||||
class="h-12 bg-white rounded-full"
|
||||
:src="$GIN_VUE_ADMIN.appLogo"
|
||||
>
|
||||
<div
|
||||
v-if="!isMobile"
|
||||
class="inline-flex font-bold text-2xl ml-2"
|
||||
>
|
||||
{{ $GIN_VUE_ADMIN.appName }}
|
||||
</div>
|
||||
|
||||
|
||||
<el-breadcrumb v-show="!isMobile" class="ml-4">
|
||||
<el-breadcrumb-item
|
||||
v-for="item in matched.slice(1, matched.length)"
|
||||
:key="item.path"
|
||||
>
|
||||
{{
|
||||
fmtTitle(item.meta.title, route)
|
||||
}}
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center">
|
||||
<tools />
|
||||
<el-dropdown>
|
||||
<div
|
||||
class="flex justify-center items-center h-full w-full"
|
||||
>
|
||||
<span
|
||||
class="cursor-pointer flex justify-center items-center text-black dark:text-gray-100"
|
||||
>
|
||||
<CustomPic />
|
||||
<span
|
||||
v-show="!isMobile"
|
||||
style="margin-left: 5px"
|
||||
>{{ userStore.userInfo.nickName }}</span>
|
||||
<el-icon>
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<span class="font-bold">
|
||||
当前角色:{{
|
||||
userStore.userInfo.authority.authorityName
|
||||
}}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<template v-if="userStore.userInfo.authorities">
|
||||
<el-dropdown-item
|
||||
v-for="item in userStore.userInfo.authorities.filter(
|
||||
(i) =>
|
||||
i.authorityId !==
|
||||
userStore.userInfo.authorityId
|
||||
)"
|
||||
:key="item.authorityId"
|
||||
@click="changeUserAuth(item.authorityId)"
|
||||
>
|
||||
<span>
|
||||
切换为:{{ item.authorityName }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
<el-dropdown-item icon="avatar" @click="toPerson">
|
||||
个人信息
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="reading-lamp"
|
||||
@click="userStore.LoginOut"
|
||||
>
|
||||
登 出
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import tools from "./tools.vue"
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
import { useUserStore } from "@/pinia/modules/user";
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAppStore } from "@/pinia"
|
||||
import { storeToRefs } from "pinia"
|
||||
import { computed, ref } from 'vue'
|
||||
import { setUserAuthority } from '@/api/user'
|
||||
import { fmtTitle } from "@/utils/fmtRouterTitle";
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const appStore = useAppStore()
|
||||
const { device } = storeToRefs(appStore)
|
||||
const isMobile = computed(() =>{
|
||||
return device.value === 'mobile'
|
||||
})
|
||||
const toPerson = () => {
|
||||
router.push({ name: "person" });
|
||||
};
|
||||
const matched = computed(() => route.meta.matched);
|
||||
|
||||
|
||||
const changeUserAuth = async (id) => {
|
||||
const res = await setUserAuthority({
|
||||
authorityId: id,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
window.sessionStorage.setItem("needCloseAll", "true");
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<!--
|
||||
@auther: bypanghu<bypanghu@163.com>
|
||||
@date: 2024/5/7
|
||||
!-->
|
||||
|
||||
<template>
|
||||
<div class="flex items-center mx-8 gap-4">
|
||||
<el-tooltip
|
||||
class=""
|
||||
effect="dark"
|
||||
content="搜索"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon @click="handleCommand" class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid">
|
||||
<Search />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
class=""
|
||||
effect="dark"
|
||||
content="系统设置"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid" @click="toggleSetting">
|
||||
<Setting />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
class=""
|
||||
effect="dark"
|
||||
content="刷新"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon
|
||||
class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
|
||||
:class="showRefreshAnmite ? 'animate-spin' : ''"
|
||||
@click="toggleRefresh"
|
||||
>
|
||||
<Refresh />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class=""
|
||||
effect="dark"
|
||||
content="切换主题"
|
||||
placement="bottom"
|
||||
:disabled="appStore.theme === 'auto'"
|
||||
>
|
||||
<el-icon v-if="appStore.theme === 'dark'" class="w-8 h-8 shadow rounded-full border border-gray-600 cursor-pointer border-solid" @click="appStore.toggleTheme(false )">
|
||||
<Sunny />
|
||||
</el-icon>
|
||||
<el-icon v-else class="w-8 h-8 shadow rounded-full border border-gray-200 cursor-pointer border-solid" @click="appStore.toggleTheme(true)">
|
||||
<Moon />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<gva-setting v-model:drawer="showSettingDrawer"></gva-setting>
|
||||
<command-menu ref="command" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { useAppStore } from "@/pinia"
|
||||
import GvaSetting from "@/view/layout/setting/index.vue"
|
||||
import { ref } from "vue"
|
||||
import { emitter } from "@/utils/bus.js";
|
||||
import CommandMenu from "@/components/commandMenu/index.vue";
|
||||
|
||||
const appStore = useAppStore()
|
||||
const showSettingDrawer = ref(false)
|
||||
const showRefreshAnmite = ref(false)
|
||||
const toggleRefresh = () =>{
|
||||
showRefreshAnmite.value = true
|
||||
emitter.emit('reload')
|
||||
setTimeout(() => {
|
||||
showRefreshAnmite.value = false
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const toggleSetting = () => {
|
||||
showSettingDrawer.value = true
|
||||
}
|
||||
|
||||
|
||||
const first = ref("");
|
||||
const command = ref();
|
||||
|
||||
const handleCommand = () => {
|
||||
command.value.open();
|
||||
};
|
||||
const initPage = () => {
|
||||
// 判断当前用户的操作系统
|
||||
if (window.localStorage.getItem("osType") === "WIN") {
|
||||
first.value = "Ctrl";
|
||||
} else {
|
||||
first.value = "⌘";
|
||||
}
|
||||
// 当用户同时按下ctrl和k键的时候
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.ctrlKey && e.key === "k") {
|
||||
// 阻止浏览器默认事件
|
||||
e.preventDefault();
|
||||
handleCommand();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
|
||||
initPage();
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
+66
-344
@@ -1,374 +1,96 @@
|
||||
<template>
|
||||
<el-container class="layout-cont">
|
||||
<el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']">
|
||||
<el-row
|
||||
:class="[isShadowBg && isMobile?'bg-black opacity-30 w-full h-full absolute top-0 left-0 z-[1001]':'']"
|
||||
@click="changeShadow()"
|
||||
/>
|
||||
<el-aside
|
||||
class="main-cont gva-aside"
|
||||
:style="{width:asideWidth()}"
|
||||
>
|
||||
<div
|
||||
class="min-h-[60px] text-center transition-all duration-300 flex items-center justify-center gap-2"
|
||||
:style="{background: backgroundColor}"
|
||||
>
|
||||
<img
|
||||
alt
|
||||
class="w-9 h-9 p-1 bg-white rounded-full"
|
||||
:src="$GIN_VUE_ADMIN.appLogo"
|
||||
>
|
||||
<div
|
||||
v-if="isSider"
|
||||
class="inline-flex font-bold text-2xl"
|
||||
:style="{color:textColor}"
|
||||
>{{ $GIN_VUE_ADMIN.appName }}</div>
|
||||
<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" class="absolute inset-0 z-[999] 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">
|
||||
<gva-tabs v-if="config.showTabs" />
|
||||
<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">
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</div>
|
||||
</router-view>
|
||||
<BottomInfo />
|
||||
</div>
|
||||
<Aside class="aside" />
|
||||
</el-aside>
|
||||
<!-- 分块滑动功能 -->
|
||||
<el-main class="main-cont main-right">
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 100 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<div
|
||||
:style="{width: `calc(100% - ${getAsideWidth()})`}"
|
||||
class="fixed top-0 box-border z-50"
|
||||
>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-header class="header-cont">
|
||||
<el-row class="p-0 h-full">
|
||||
<el-col
|
||||
:xs="2"
|
||||
:lg="1"
|
||||
:md="1"
|
||||
:sm="1"
|
||||
:xl="1"
|
||||
class="z-50 flex items-center pl-3"
|
||||
>
|
||||
<div
|
||||
class="text-black cursor-pointer text-lg leading-5"
|
||||
@click="totalCollapse"
|
||||
>
|
||||
<div
|
||||
v-if="isCollapse"
|
||||
class="gvaIcon gvaIcon-arrow-double-right"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="gvaIcon gvaIcon-arrow-double-left"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="10"
|
||||
:lg="14"
|
||||
:md="14"
|
||||
:sm="9"
|
||||
:xl="14"
|
||||
:pull="1"
|
||||
class="flex items-center"
|
||||
>
|
||||
<!-- 修改为手机端不显示顶部标签 -->
|
||||
<el-breadcrumb
|
||||
v-show="!isMobile"
|
||||
class="breadcrumb"
|
||||
>
|
||||
<el-breadcrumb-item
|
||||
v-for="item in matched.slice(1,matched.length)"
|
||||
:key="item.path"
|
||||
>{{ fmtTitle(item.meta.title,route) }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="12"
|
||||
:lg="9"
|
||||
:md="9"
|
||||
:sm="14"
|
||||
:xl="9"
|
||||
class="flex items-center justify-end"
|
||||
>
|
||||
<div class="mr-1.5 flex items-center">
|
||||
<Search />
|
||||
<el-dropdown>
|
||||
<div class="flex justify-center items-center h-full w-full">
|
||||
<span class="cursor-pointer flex justify-center items-center">
|
||||
<CustomPic />
|
||||
<span
|
||||
v-show="!isMobile"
|
||||
style="margin-left: 5px"
|
||||
>{{ userStore.userInfo.nickName }}</span>
|
||||
<el-icon>
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<span class="font-bold">
|
||||
当前角色:{{ userStore.userInfo.authority.authorityName }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<template v-if="userStore.userInfo.authorities">
|
||||
<el-dropdown-item
|
||||
v-for="item in userStore.userInfo.authorities.filter(i=>i.authorityId!==userStore.userInfo.authorityId)"
|
||||
:key="item.authorityId"
|
||||
@click="changeUserAuth(item.authorityId)"
|
||||
>
|
||||
<span>
|
||||
切换为:{{ item.authorityName }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
<el-dropdown-item icon="avatar">
|
||||
<div
|
||||
class="command-box"
|
||||
style="display: flex"
|
||||
@click="handleCommand"
|
||||
>
|
||||
<div>指令菜单</div>
|
||||
<div style="margin-left: 8px">
|
||||
<span class="button">{{ first }}</span>
|
||||
+
|
||||
<span class="button">K</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="avatar"
|
||||
@click="toPerson"
|
||||
>个人信息</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="reading-lamp"
|
||||
@click="userStore.LoginOut"
|
||||
>登 出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 当前面包屑用路由自动生成可根据需求修改 -->
|
||||
<!--
|
||||
:to="{ path: item.path }" 暂时注释不用-->
|
||||
<HistoryComponent ref="layoutHistoryComponent" />
|
||||
</div>
|
||||
</transition>
|
||||
<router-view
|
||||
v-if="reloadFlag"
|
||||
v-slot="{ Component }"
|
||||
class="admin-box"
|
||||
>
|
||||
<div
|
||||
id="gva-base-load-dom"
|
||||
>
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</div>
|
||||
</router-view>
|
||||
<BottomInfo />
|
||||
<setting />
|
||||
<CommandMenu ref="command" />
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
</el-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Aside from '@/view/layout/aside/index.vue'
|
||||
import HistoryComponent from '@/view/layout/aside/historyComponent/history.vue'
|
||||
import Search from '@/view/layout/search/search.vue'
|
||||
import BottomInfo from '@/view/layout/bottomInfo/bottomInfo.vue'
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
import CommandMenu from '@/components/commandMenu/index.vue'
|
||||
import Setting from './setting/index.vue'
|
||||
import { setUserAuthority } from '@/api/user'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { computed, ref, onMounted, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
import { fmtTitle } from '@/utils/fmtRouterTitle'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
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 BottomInfo from "@/view/layout/bottomInfo/bottomInfo.vue";
|
||||
import { emitter } from "@/utils/bus.js";
|
||||
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)
|
||||
|
||||
defineOptions({
|
||||
name: 'Layout',
|
||||
name: "GvaLayout",
|
||||
});
|
||||
|
||||
useResponsive(true)
|
||||
const font = reactive({
|
||||
color: 'rgba(0, 0, 0, .15)',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const routerStore = useRouterStore()
|
||||
// 三种窗口适配
|
||||
const isCollapse = ref(false)
|
||||
const isSider = ref(true)
|
||||
const isMobile = ref(false)
|
||||
watchEffect(()=>{
|
||||
font.color = theme.value === 'dark' ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'
|
||||
|
||||
const first = ref('')
|
||||
const initPage = () => {
|
||||
// 判断当前用户的操作系统
|
||||
if (window.localStorage.getItem('osType') === 'WIN') {
|
||||
first.value = 'Ctrl'
|
||||
} else {
|
||||
first.value = '⌘'
|
||||
}
|
||||
// 当用户同时按下ctrl和k键的时候
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.ctrlKey && e.key === 'k') {
|
||||
// 阻止浏览器默认事件
|
||||
e.preventDefault()
|
||||
handleCommand()
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
})
|
||||
|
||||
const screenWidth = document.body.clientWidth
|
||||
if (screenWidth < 1000) {
|
||||
isMobile.value = true
|
||||
isSider.value = false
|
||||
isCollapse.value = true
|
||||
} else if (screenWidth >= 1000 && screenWidth < 1200) {
|
||||
isMobile.value = false
|
||||
isSider.value = false
|
||||
isCollapse.value = true
|
||||
} else {
|
||||
isMobile.value = false
|
||||
isSider.value = true
|
||||
isCollapse.value = false
|
||||
}
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
const command = ref()
|
||||
const handleCommand = () => {
|
||||
command.value.open()
|
||||
}
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const routerStore = useRouterStore();
|
||||
|
||||
onMounted(() => {
|
||||
// 挂载一些通用的事件
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
emitter.emit('mobile', isMobile.value)
|
||||
emitter.on('reload', reload)
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
initPage()
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
emitter.emit('mobile', isMobile.value)
|
||||
})()
|
||||
}
|
||||
emitter.on("reload", reload);
|
||||
if (userStore.loadingInstance) {
|
||||
userStore.loadingInstance.close()
|
||||
userStore.loadingInstance.close();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const userStore = useUserStore()
|
||||
const userStore = useUserStore();
|
||||
|
||||
const asideWidth = () => {
|
||||
if (isMobile.value) {
|
||||
return isCollapse.value ? '0px' : '220px'
|
||||
}
|
||||
return isCollapse.value ? '54px' : '220px'
|
||||
}
|
||||
|
||||
const getAsideWidth = () => {
|
||||
if (isMobile.value) return '0px'
|
||||
return isCollapse.value ? '54px' : '220px'
|
||||
}
|
||||
|
||||
const textColor = computed(() => {
|
||||
if (userStore.sideMode === 'dark') {
|
||||
return '#fff'
|
||||
} else if (userStore.sideMode === 'light') {
|
||||
return '#191a23'
|
||||
} else {
|
||||
return userStore.baseColor
|
||||
}
|
||||
})
|
||||
|
||||
const backgroundColor = computed(() => {
|
||||
if (userStore.sideMode === 'dark') {
|
||||
return '#191a23'
|
||||
} else if (userStore.sideMode === 'light') {
|
||||
return '#fff'
|
||||
} else {
|
||||
return userStore.sideMode
|
||||
}
|
||||
})
|
||||
|
||||
const matched = computed(() => route.meta.matched)
|
||||
|
||||
const changeUserAuth = async(id) => {
|
||||
const res = await setUserAuthority({
|
||||
authorityId: id
|
||||
})
|
||||
if (res.code === 0) {
|
||||
window.sessionStorage.setItem('needCloseAll', 'true')
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
const reloadFlag = ref(true)
|
||||
let reloadTimer = null
|
||||
const reload = async() => {
|
||||
const reloadFlag = ref(true);
|
||||
let reloadTimer = null;
|
||||
const reload = async () => {
|
||||
if (reloadTimer) {
|
||||
window.clearTimeout(reloadTimer)
|
||||
window.clearTimeout(reloadTimer);
|
||||
}
|
||||
reloadTimer = window.setTimeout(async() => {
|
||||
reloadTimer = window.setTimeout(async () => {
|
||||
if (route.meta.keepAlive) {
|
||||
reloadFlag.value = false
|
||||
await nextTick()
|
||||
reloadFlag.value = true
|
||||
reloadFlag.value = false;
|
||||
await nextTick();
|
||||
reloadFlag.value = true;
|
||||
} else {
|
||||
const title = route.meta.title
|
||||
router.push({ name: 'Reload', params: { title }})
|
||||
const title = route.meta.title;
|
||||
router.push({ name: "Reload", params: { title } });
|
||||
}
|
||||
}, 400)
|
||||
}
|
||||
}, 400);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const isShadowBg = ref(false)
|
||||
const totalCollapse = () => {
|
||||
isCollapse.value = !isCollapse.value
|
||||
isSider.value = !isCollapse.value
|
||||
isShadowBg.value = !isCollapse.value
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
}
|
||||
|
||||
const toPerson = () => {
|
||||
router.push({ name: 'person' })
|
||||
}
|
||||
const changeShadow = () => {
|
||||
isShadowBg.value = !isShadowBg.value
|
||||
isSider.value = !!isCollapse.value
|
||||
totalCollapse()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.button {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: rgb(250,250,250);
|
||||
width: 25px!important;
|
||||
padding: 4px 8px !important;
|
||||
border: 1px solid #eaeaea;
|
||||
margin-right: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep .el-overlay {
|
||||
background-color: hsla(0,0%,100%,.9) !important;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="search-component">
|
||||
<div class="search-component items-center">
|
||||
<div
|
||||
class="gvaIcon gvaIcon-refresh"
|
||||
:class="[reload ? 'reloading' : '']"
|
||||
@@ -10,18 +10,35 @@
|
||||
class="gvaIcon gvaIcon-customer-service"
|
||||
@click="toService"
|
||||
/>
|
||||
<el-switch
|
||||
v-model="isDark"
|
||||
:active-action-icon="Moon"
|
||||
:inactive-action-icon="Sunny"
|
||||
@change="handleDarkSwitch"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Screenfull from '@/view/layout/screenfull/index.vue'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { ref } from 'vue'
|
||||
import { Sunny, Moon } from '@element-plus/icons-vue'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'BtnBox',
|
||||
})
|
||||
const isDark = ref(localStorage.getItem('isDark') === 'true' || true )
|
||||
|
||||
watchEffect(() =>{
|
||||
if (isDark.value) {
|
||||
document.documentElement.classList.add('dark')
|
||||
localStorage.setItem('isDark', true)
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
localStorage.setItem('isDark', false)
|
||||
}
|
||||
})
|
||||
const reload = ref(false)
|
||||
const handleReload = () => {
|
||||
reload.value = true
|
||||
@@ -34,11 +51,15 @@ const toService = () => {
|
||||
window.open('https://support.qq.com/product/371961')
|
||||
}
|
||||
|
||||
const handleDarkSwitch = (e) =>{
|
||||
isDark.value = e
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
.search-component {
|
||||
@apply inline-flex overflow-hidden text-center gap-5 mr-5;
|
||||
@apply inline-flex overflow-hidden text-center gap-5 mr-5 text-black dark:text-gray-100;
|
||||
div{
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
@@ -1,153 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="drawer-container"
|
||||
icon="setting"
|
||||
@click="showSettingDrawer"
|
||||
/>
|
||||
<el-drawer
|
||||
v-model="drawer"
|
||||
title="系统配置"
|
||||
:direction="direction"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div class="setting_body">
|
||||
<div class="setting_card">
|
||||
<div class="setting_content">
|
||||
<div class="theme-box">
|
||||
<div
|
||||
class="item"
|
||||
@click="changeMode('light')"
|
||||
>
|
||||
<div class="item-top">
|
||||
<el-icon
|
||||
v-if="userStore.mode === 'light'"
|
||||
class="check"
|
||||
>
|
||||
<check />
|
||||
</el-icon>
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/NQ%24zoisaD2/jpRkZQMyYRryryPNtyIC.svg">
|
||||
</div>
|
||||
<p>
|
||||
简约白
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
@click="changeMode('dark')"
|
||||
>
|
||||
<div class="item-top">
|
||||
<el-icon
|
||||
v-if="userStore.mode === 'dark'"
|
||||
class="check"
|
||||
>
|
||||
<check />
|
||||
</el-icon>
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg">
|
||||
</div>
|
||||
<p>
|
||||
商务黑
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<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="mt-2 text-sm p-2 flex items-center gap-2">
|
||||
<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="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)">
|
||||
<el-icon v-if="config.primaryColor === item">
|
||||
<Select />
|
||||
</el-icon>
|
||||
</div>
|
||||
<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="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" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>灰色模式</div>
|
||||
<el-switch v-model="config.grey" @change="appStore.toggleGrey" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>色弱模式</div>
|
||||
<el-switch v-model="config.weakness" @change="appStore.toggleWeakness" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div>显示标签页</div>
|
||||
<el-switch v-model="config.showTabs" @change="appStore.toggleTabs" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
<div class="mb-8">
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<el-alert type="warning" :closable="false">
|
||||
请注意,所有配置请保存到本地文件的 <el-tag>config.json</el-tag> 文件中,否则刷新页面后会丢失配置
|
||||
</el-alert>
|
||||
|
||||
<el-button type="primary" class="mt-4 " @click="copyConfig">复制配置json</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
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: 'Setting',
|
||||
name: 'GvaSetting',
|
||||
})
|
||||
|
||||
const drawer = ref(false)
|
||||
const direction = ref('rtl')
|
||||
const width = computed(() => {
|
||||
return device.value === 'mobile' ? '100%' : '500px'
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const colors = [
|
||||
'#EB2F96',
|
||||
'#3b82f6',
|
||||
'#2FEB54',
|
||||
'#EBEB2F',
|
||||
'#EB2F2F',
|
||||
'#2FEBEB',
|
||||
]
|
||||
|
||||
const handleClose = () => {
|
||||
drawer.value = false
|
||||
}
|
||||
const showSettingDrawer = () => {
|
||||
drawer.value = true
|
||||
}
|
||||
const changeMode = (e) => {
|
||||
if (e === null) {
|
||||
userStore.changeSideMode('dark')
|
||||
return
|
||||
}
|
||||
userStore.changeSideMode(e)
|
||||
const drawer = defineModel('drawer',{
|
||||
default: true,
|
||||
type : Boolean
|
||||
})
|
||||
|
||||
const options = ['dark','light','auto']
|
||||
|
||||
const copyConfig = () => {
|
||||
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('复制成功, 请自行保存到本地文件中')
|
||||
}
|
||||
|
||||
const customColor = ref("")
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-container {
|
||||
transition: all 0.2s;
|
||||
&:hover{
|
||||
right: 0
|
||||
}
|
||||
position: fixed;
|
||||
right: -20px;
|
||||
bottom: 15%;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
color: #fff;
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0 ,0 ,0, 10%);
|
||||
::v-deep(.el-drawer__header){
|
||||
@apply border-gray-400 dark:border-gray-600;
|
||||
}
|
||||
.setting_body{
|
||||
padding: 20px;
|
||||
.setting_card{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.setting_content{
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
>.theme-box{
|
||||
display: flex;
|
||||
}
|
||||
>.color-box{
|
||||
div{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.item{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin-right: 20px;
|
||||
.item-top{
|
||||
position: relative;
|
||||
}
|
||||
.check{
|
||||
position: absolute;
|
||||
font-size: 20px;
|
||||
color: #00afff;
|
||||
right:10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
p{
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
+73
-61
@@ -1,9 +1,14 @@
|
||||
<template>
|
||||
<div class="router-history">
|
||||
<!--
|
||||
@auther: bypanghu<bypanghu@163.com>
|
||||
@date: 2024/5/7
|
||||
!-->
|
||||
<template>
|
||||
<div class="gva-tabs">
|
||||
<el-tabs
|
||||
v-model="activeValue"
|
||||
:closable="!(historys.length === 1 && $route.name === defaultRouter)"
|
||||
type="card"
|
||||
class=" bg-white text-slate-700 dark:text-slate-500 dark:bg-slate-900"
|
||||
@contextmenu.prevent="openContextMenu($event)"
|
||||
@tab-click="changeTab"
|
||||
@tab-remove="removeTab"
|
||||
@@ -14,20 +19,14 @@
|
||||
:label="item.meta.title"
|
||||
:name="getFmtString(item)"
|
||||
:tab="item"
|
||||
class="gva-tab"
|
||||
class="border-none "
|
||||
>
|
||||
<template #label>
|
||||
<span
|
||||
:tab="item"
|
||||
:style="{
|
||||
color: activeValue === getFmtString(item) ? userStore.activeColor : '#333',
|
||||
}"
|
||||
:class="activeValue === getFmtString(item) ? 'text-active' : 'text-gray-600 dark:text-slate-400 '"
|
||||
><i
|
||||
class="dot"
|
||||
:style="{
|
||||
backgroundColor:
|
||||
activeValue === getFmtString(item) ? userStore.activeColor : '#ddd',
|
||||
}"
|
||||
:class="activeValue === getFmtString(item) ? 'text-active' : 'text-gray-600 dark:text-slate-400'"
|
||||
/>
|
||||
{{ fmtTitle(item.meta.title,item) }}</span>
|
||||
</template>
|
||||
@@ -40,10 +39,18 @@
|
||||
:style="{ left: left + 'px', top: top + 'px' }"
|
||||
class="contextmenu"
|
||||
>
|
||||
<li @click="closeAll">关闭所有</li>
|
||||
<li @click="closeLeft">关闭左侧</li>
|
||||
<li @click="closeRight">关闭右侧</li>
|
||||
<li @click="closeOther">关闭其他</li>
|
||||
<li @click="closeAll">
|
||||
关闭所有
|
||||
</li>
|
||||
<li @click="closeLeft">
|
||||
关闭左侧
|
||||
</li>
|
||||
<li @click="closeRight">
|
||||
关闭右侧
|
||||
</li>
|
||||
<li @click="closeOther">
|
||||
关闭其他
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -80,8 +87,8 @@ const rightActive = ref('')
|
||||
const defaultRouter = computed(() => userStore.userInfo.authority.defaultRouter)
|
||||
const openContextMenu = (e) => {
|
||||
if (
|
||||
historys.value.length === 1 &&
|
||||
route.name === defaultRouter.value
|
||||
historys.value.length === 1 &&
|
||||
route.name === defaultRouter.value
|
||||
) {
|
||||
return false
|
||||
}
|
||||
@@ -93,16 +100,8 @@ const openContextMenu = (e) => {
|
||||
}
|
||||
if (id) {
|
||||
contextMenuVisible.value = true
|
||||
let width
|
||||
if (isCollapse.value) {
|
||||
width = 54
|
||||
} else {
|
||||
width = 220
|
||||
}
|
||||
if (isMobile.value) {
|
||||
width = 0
|
||||
}
|
||||
left.value = e.clientX - width
|
||||
|
||||
left.value = e.clientX
|
||||
top.value = e.clientY + 10
|
||||
rightActive.value = id.substring(4)
|
||||
}
|
||||
@@ -131,7 +130,7 @@ const closeLeft = () => {
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
const activeIndex = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value.splice(0, rightIndex)
|
||||
if (rightIndex > activeIndex) {
|
||||
@@ -148,7 +147,7 @@ const closeRight = () => {
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
const activeIndex = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value.splice(leftIndex + 1, historys.value.length)
|
||||
if (leftIndex < activeIndex) {
|
||||
@@ -213,7 +212,7 @@ const changeTab = (TabsPaneContext) => {
|
||||
}
|
||||
const removeTab = (tab) => {
|
||||
const index = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === tab
|
||||
(item) => getFmtString(item) === tab
|
||||
)
|
||||
if (getFmtString(route) === tab) {
|
||||
if (historys.value.length === 1) {
|
||||
@@ -288,7 +287,7 @@ const initPage = () => {
|
||||
|
||||
emitter.on('setQuery', (data) => {
|
||||
const index = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value[index].query = data
|
||||
activeValue.value = getFmtString(historys.value[index])
|
||||
@@ -346,40 +345,53 @@ onUnmounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.contextmenu {
|
||||
width: 100px;
|
||||
margin: 0;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.el-tabs__item .el-icon-close {
|
||||
color: initial !important;
|
||||
}
|
||||
.el-tabs__item .dot {
|
||||
content: "";
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s;
|
||||
@apply bg-white dark:bg-slate-900 w-28 m-0 py-2.5 px-0 border border-gray-200 text-sm shadow-md rounded absolute z-50 border-solid dark:border-slate-800;
|
||||
}
|
||||
|
||||
.contextmenu li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
@apply text-slate-700 dark:text-slate-200 text-base list-none px-4 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-600 cursor-pointer;
|
||||
}
|
||||
.contextmenu li:hover {
|
||||
background: #f2f2f2;
|
||||
cursor: pointer;
|
||||
|
||||
$base-tag-item-height : 4rem;
|
||||
|
||||
.gva-tabs {
|
||||
::v-deep(.el-tabs--card > .el-tabs__header){
|
||||
border:none;
|
||||
}
|
||||
::v-deep(.el-tabs__nav-scroll) {
|
||||
padding: 4px 4px;
|
||||
}
|
||||
|
||||
::v-deep(.el-tabs__nav) {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
::v-deep(.el-tabs__header) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
::v-deep(.el-tabs__item){
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--el-border-color-darker);
|
||||
border-radius: 2px;
|
||||
margin-right: 5px;
|
||||
margin-left: 2px;
|
||||
transition: padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;
|
||||
height: 34px;
|
||||
&.is-active {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
::v-deep(.el-tabs__item):first-child{
|
||||
border: 1px solid var(--el-border-color-darker);
|
||||
&.is-active {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user