settings.js 参数结构调整,ThemeSetting 组件重构并更名为 AppSetting

This commit is contained in:
hooray 2022-01-16 05:23:01 +08:00
parent 8e24cb6000
commit b0e3bd2fb5
21 changed files with 499 additions and 544 deletions

View File

@ -1,5 +1,5 @@
<template>
<el-config-provider :size="settingsStore.elementSize">
<el-config-provider :size="settingsStore.app.elementSize">
<RouterView
:style="{
'--g-main-sidebar-actual-width': mainSidebarActualWidth,
@ -17,7 +17,7 @@ const settingsStore = useSettingsStore()
const mainSidebarActualWidth = computed(() => {
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width')
actualWidth = parseInt(actualWidth)
if (['head', 'single'].includes(settingsStore.menuMode)) {
if (['head', 'single'].includes(settingsStore.menu.menuMode)) {
actualWidth = 0
}
return `${actualWidth}px`
@ -27,7 +27,7 @@ const mainSidebarActualWidth = computed(() => {
const subSidebarActualWidth = computed(() => {
let actualWidth = getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width')
actualWidth = parseInt(actualWidth)
if (settingsStore.sidebarCollapse) {
if (settingsStore.menu.subMenuCollapse) {
actualWidth = 64
}
return `${actualWidth}px`
@ -35,12 +35,12 @@ const subSidebarActualWidth = computed(() => {
watch(() => settingsStore.mode, () => {
if (settingsStore.mode === 'pc') {
settingsStore.updateThemeSetting({
'sidebarCollapse': settingsStore.sidebarCollapseLastStatus
settingsStore.$patch(state => {
state.menu.subMenuCollapse = settingsStore.subMenuCollapseLastStatus
})
} else if (settingsStore.mode === 'mobile') {
settingsStore.updateThemeSetting({
'sidebarCollapse': true
settingsStore.$patch(state => {
state.menu.subMenuCollapse = true
})
}
document.body.setAttribute('data-mode', settingsStore.mode)
@ -48,17 +48,17 @@ watch(() => settingsStore.mode, () => {
immediate: true
})
watch(() => settingsStore.menuMode, () => {
document.body.setAttribute('data-menu-mode', settingsStore.menuMode)
watch(() => settingsStore.menu.menuMode, () => {
document.body.setAttribute('data-menu-mode', settingsStore.menu.menuMode)
}, {
immediate: true
})
watch([
() => settingsStore.enableDynamicTitle,
() => settingsStore.app.enableDynamicTitle,
() => settingsStore.title
], () => {
if (settingsStore.enableDynamicTitle && settingsStore.title) {
if (settingsStore.app.enableDynamicTitle && settingsStore.title) {
let title = settingsStore.title
document.title = `${title} - ${import.meta.env.VITE_APP_TITLE}`
} else {

View File

@ -1,8 +1,8 @@
<template>
<footer class="copyright">
Copyright © {{ settingsStore.copyrightDates }}
<a v-if="settingsStore.copyrightWebsite" :href="settingsStore.copyrightWebsite" target="_blank" rel="noopener">{{ settingsStore.copyrightCompany }},</a>
<span v-else>{{ settingsStore.copyrightCompany }},</span>
Copyright © {{ settingsStore.copyright.dates }}
<a v-if="settingsStore.copyright.website" :href="settingsStore.copyright.website" target="_blank" rel="noopener">{{ settingsStore.copyright.company }},</a>
<span v-else>{{ settingsStore.copyright.company }},</span>
All Rights Reserved
</footer>
</template>

View File

@ -0,0 +1,357 @@
<template>
<div>
<el-drawer v-model="isShow" title="应用配置" direction="rtl" :size="350" custom-class="flex-container">
<div class="container">
<el-alert title="应用配置可实时预览效果,但仅是临时生效,要想真正作用于项目,可以点击下方的“复制配置”按钮,将配置粘贴到 src/settings.custom.json 中即可,或者也可在 src/settings.js 中直接修改默认配置。同时建议在生产环境隐藏应用配置功能。" type="error" :closable="false" />
<el-divider v-if="settingsStore.mode === 'pc'">导航栏模式</el-divider>
<div v-if="settingsStore.mode === 'pc'" class="menu-mode">
<el-tooltip content="侧边栏模式(含主导航)" placement="top" :show-after="500" :append-to-body="false">
<div class="mode mode-side" :class="{'active': settings.menu.menuMode === 'side'}" @click="settings.menu.menuMode = 'side'">
<svg-icon name="el-icon-check" />
</div>
</el-tooltip>
<el-tooltip content="顶部模式" placement="top" :show-after="500" :append-to-body="false">
<div class="mode mode-head" :class="{'active': settings.menu.menuMode === 'head'}" @click="settings.menu.menuMode = 'head'">
<svg-icon name="el-icon-check" />
</div>
</el-tooltip>
<el-tooltip content="侧边栏模式(不含主导航)" placement="top" :show-after="500" :append-to-body="false">
<div class="mode mode-single" :class="{'active': settings.menu.menuMode === 'single'}" @click="settings.menu.menuMode = 'single'">
<svg-icon name="el-icon-check" />
</div>
</el-tooltip>
</div>
<el-divider>导航栏</el-divider>
<div class="setting-item">
<div class="label">是否折叠</div>
<el-switch v-model="settings.menu.subMenuCollapse" />
</div>
<div class="setting-item">
<div class="label">
切换跳转
<el-tooltip content="开启该功能后,切换侧边栏时,页面自动跳转至该侧边栏导航下第一个路由地址" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.menu.switchMainMenuAndPageJump" :disabled="['single'].includes(settings.menu.menuMode)" />
</div>
<div class="setting-item">
<div class="label">
保持展开一个
<el-tooltip content="开启该功能后,侧边栏只保持一个子菜单的展开" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.menu.subMenuUniqueOpened" />
</div>
<el-divider>顶栏</el-divider>
<div class="setting-item">
<div class="label">是否固定</div>
<el-switch v-model="settings.topbar.fixed" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">折叠按钮</div>
<el-switch v-model="settings.topbar.enableSidebarCollapse" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">面包屑导航</div>
<el-switch v-model="settings.topbar.enableBreadcrumb" />
</div>
<div class="setting-item">
<div class="label">
导航栏搜索
<el-tooltip content="对导航栏进行快捷搜索" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.topbar.enableNavSearch" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">
全屏
<el-tooltip content="该功能使用场景极少,用户习惯于通过窗口“最大化”功能来扩大显示区域,以显示更多内容,并且使用 F11 键也可以进入全屏效果" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.topbar.enableFullscreen" />
</div>
<div class="setting-item">
<div class="label">
页面刷新
<el-tooltip content="开启时会阻止原生 F5 键刷新功能,并采用框架提供的刷新模式进行页面刷新" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.topbar.enablePageReload" />
</div>
<el-divider>底部版权</el-divider>
<div class="setting-item">
<div class="label">是否启用</div>
<el-switch v-model="settings.copyright.enable" />
</div>
<div class="setting-item">
<div class="label">日期</div>
<el-input v-model="settings.copyright.dates" size="small" :disabled="!settings.copyright.enable" />
</div>
<div class="setting-item">
<div class="label">公司</div>
<el-input v-model="settings.copyright.company" size="small" :disabled="!settings.copyright.enable" />
</div>
<div class="setting-item">
<div class="label">网址</div>
<el-input v-model="settings.copyright.website" size="small" :disabled="!settings.copyright.enable" />
</div>
<el-divider>控制台</el-divider>
<div class="setting-item">
<div class="label">
是否开启
<el-tooltip content="该功能开启时,登录成功默认进入控制台页面,反之则默认进入导航栏里第一个导航页面" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.dashboard.enable" />
</div>
<div class="setting-item">
<div class="label">控制台名称</div>
<el-input v-model="settings.dashboard.title" size="small" />
</div>
<el-divider>其它</el-divider>
<div class="setting-item">
<div class="label">
组件尺寸
<el-tooltip content="全局设置 Element Plus 组件的默认尺寸大小" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-radio-group v-model="settings.app.elementSize" size="small">
<el-radio-button label="large">较大</el-radio-button>
<el-radio-button label="default">默认</el-radio-button>
<el-radio-button label="small">稍小</el-radio-button>
</el-radio-group>
</div>
<div class="setting-item">
<div class="label">是否启用权限</div>
<el-switch v-model="settings.app.enablePermission" />
</div>
<div class="setting-item">
<div class="label">是否开启后端返回路由数据</div>
<el-switch v-model="settings.app.enableBackendReturnRoute" />
</div>
<div class="setting-item">
<div class="label">
载入进度条
<el-tooltip content="该功能开启时,跳转路由会看到页面顶部有进度条" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.app.enableProgress" />
</div>
<div class="setting-item">
<div class="label">
动态标题
<el-tooltip content="该功能开启时,页面标题会显示当前路由标题,格式为“页面标题 - 网站名称”;关闭时则显示网站名称,网站名称在项目根目录下 .env.* 文件里配置" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="settings.app.enableDynamicTitle" />
</div>
</div>
<div v-if="isSupported" class="action-buttons">
<el-button icon="el-icon-document-copy" type="primary" @click="handleCopy">复制配置</el-button>
</div>
</el-drawer>
</div>
</template>
<script setup>
const { proxy } = getCurrentInstance()
const route = useRoute()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const isShow = ref(false)
import globalSettings from '@/settings'
const settings = ref(globalSettings)
watch(() => settings, () => {
settingsStore.updateThemeSetting(settings.value)
menuStore.switchHeaderActived(0)
settings.value.menu.menuMode !== 'single' && menuStore.setHeaderActived(route.fullPath)
}, {
deep: true
})
onMounted(() => {
proxy.$eventBus.on('global-theme-toggle', () => {
isShow.value = !isShow.value
})
})
import { useClipboard } from '@vueuse/core'
const { copy, copied, isSupported } = useClipboard()
watch(copied, val => {
if (val) {
proxy.$message.success('复制成功,请粘贴到 src/settings.custom.json 文件中!')
}
})
function handleCopy() {
copy(JSON.stringify(settings.value, null, 4))
}
</script>
<style lang="scss" scoped>
:deep(.el-drawer__body) {
display: flex;
flex-direction: column;
overflow: auto;
padding: 0;
}
:deep(.el-drawer__header) {
margin-bottom: initial;
padding-bottom: 20px;
border-bottom: 1px solid #ddd;
}
.flex-container {
.container {
padding: 15px;
overflow: auto;
flex: 1;
}
.action-buttons {
padding: 15px;
text-align: center;
background-color: #fff;
border-top: 1px solid #ddd;
.el-button {
width: 100%;
}
}
}
:deep(.el-divider) {
margin: 36px 0 24px;
}
.menu-mode {
display: flex;
align-items: center;
justify-content: space-around;
padding-bottom: 10px;
.mode {
position: relative;
width: 50px;
height: 40px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
background-color: #e1e3e6;
box-shadow: 0 0 15px 1px #aaa;
transition: 0.3s;
&:hover {
box-shadow: 0 0 15px 1px #666;
}
&.active {
box-shadow: 0 0 0 2px #409eff;
}
&::before,
&::after {
pointer-events: none;
}
&-side {
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 10px;
height: 100%;
background-color: #222b45;
}
&::after {
content: "";
position: absolute;
top: 0;
left: 10px;
width: 15px;
height: 100%;
background-color: #fff;
}
}
&-head {
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 10px;
background-color: #222b45;
}
&::after {
content: "";
position: absolute;
top: 10px;
left: 0;
width: 15px;
height: calc(100% - 10px);
background-color: #fff;
}
}
&-single {
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 15px;
height: 100%;
background-color: #fff;
}
}
i {
position: absolute;
right: 2px;
bottom: 2px;
display: none;
}
&.active i {
display: block;
color: #409eff;
}
}
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 5px 0;
padding: 5px 10px;
border-radius: 5px;
transition: all 0.3s;
&:hover {
background: #f1f1f1;
}
.label {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
i {
margin-left: 4px;
font-size: 17px;
color: #e6a23c;
cursor: help;
}
}
.el-switch {
height: auto;
}
.el-input {
width: 150px;
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<transition name="header">
<header v-if="settingsStore.mode === 'pc' && settingsStore.menuMode === 'head'">
<header v-if="settingsStore.mode === 'pc' && settingsStore.menu.menuMode === 'head'">
<div class="header-container">
<div class="main">
<Logo />

View File

@ -1,5 +1,5 @@
<template>
<router-link :to="to" class="title" :class="{'is-link': settingsStore.enableDashboard}" :title="title">
<router-link :to="to" class="title" :class="{'is-link': settingsStore.dashboard.enable}" :title="title">
<img v-if="showLogo" :src="logo" class="logo">
<span v-if="showTitle">{{ title }}</span>
</router-link>
@ -27,7 +27,7 @@ const logo = ref(imgLogo)
const to = computed(() => {
let rtn = {}
if (settingsStore.enableDashboard) {
if (settingsStore.dashboard.enable) {
rtn.name = 'dashboard'
}
return rtn

View File

@ -1,6 +1,6 @@
<template>
<transition name="main-sidebar">
<div v-if="settingsStore.menuMode === 'side' || (settingsStore.mode === 'mobile' && settingsStore.menuMode !== 'single')" class="main-sidebar-container">
<div v-if="settingsStore.menu.menuMode === 'side' || (settingsStore.mode === 'mobile' && settingsStore.menu.menuMode !== 'single')" class="main-sidebar-container">
<Logo :show-title="false" class="sidebar-logo" />
<!-- 侧边栏模式含主导航 -->
<div class="nav">

View File

@ -116,7 +116,7 @@ onMounted(() => {
isShow.value = !isShow.value
})
proxy.$hotkeys('alt+s', e => {
if (settingsStore.enableNavSearch) {
if (settingsStore.topbar.enableNavSearch) {
e.preventDefault()
isShow.value = true
}

View File

@ -1,16 +1,16 @@
<template>
<div v-if="['side', 'head', 'single'].includes(settingsStore.menuMode) || settingsStore.mode === 'mobile'" class="sub-sidebar-container" :class="{'is-collapse': settingsStore.mode === 'pc' && settingsStore.sidebarCollapse}" @scroll="onSidebarScroll">
<div v-if="['side', 'head', 'single'].includes(settingsStore.menu.menuMode) || settingsStore.mode === 'mobile'" class="sub-sidebar-container" :class="{'is-collapse': settingsStore.mode === 'pc' && settingsStore.menu.subMenuCollapse}" @scroll="onSidebarScroll">
<Logo
:show-logo="settingsStore.menuMode === 'single'" :class="{
:show-logo="settingsStore.menu.menuMode === 'single'" :class="{
'sidebar-logo': true,
'sidebar-logo-bg': settingsStore.menuMode === 'single',
'sidebar-logo-bg': settingsStore.menu.menuMode === 'single',
'shadow': sidebarScrollTop
}"
/>
<!-- 侧边栏模式无主导航 -->
<el-menu
:unique-opened="settingsStore.sidebarUniqueOpened" :default-openeds="menuStore.defaultOpenedPaths" :default-active="$route.meta.activeMenu || $route.path" :collapse="settingsStore.mode === 'pc' && settingsStore.sidebarCollapse" :collapse-transition="false" :class="{
'is-collapse-without-logo': settingsStore.menuMode !== 'single' && settingsStore.sidebarCollapse
:unique-opened="settingsStore.menu.subMenuUniqueOpened" :default-openeds="menuStore.defaultOpenedPaths" :default-active="$route.meta.activeMenu || $route.path" :collapse="settingsStore.mode === 'pc' && settingsStore.menu.subMenuCollapse" :collapse-transition="false" :class="{
'is-collapse-without-logo': settingsStore.menu.menuMode !== 'single' && settingsStore.menu.subMenuCollapse
}"
>
<transition-group name="sub-sidebar">

View File

@ -1,423 +0,0 @@
<template>
<teleport to="#app">
<el-drawer v-model="isShow" title="主题配置" direction="rtl" :size="300">
<el-alert title="主题配置可实时预览效果,更多设置请在 src/settings.js 中进行设置,建议在生产环境隐藏主题配置功能" type="error" :closable="false" />
<el-divider v-if="settingsStore.mode === 'pc'">导航栏模式</el-divider>
<div v-if="settingsStore.mode === 'pc'" class="menu-mode">
<el-tooltip content="侧边栏模式(含主导航)" placement="top" :show-after="500" :append-to-body="false">
<div class="mode mode-side" :class="{'active': menuMode === 'side'}" @click="menuMode = 'side'">
<svg-icon name="el-icon-check" />
</div>
</el-tooltip>
<el-tooltip content="顶部模式" placement="top" :show-after="500" :append-to-body="false">
<div class="mode mode-head" :class="{'active': menuMode === 'head'}" @click="menuMode = 'head'">
<svg-icon name="el-icon-check" />
</div>
</el-tooltip>
<el-tooltip content="侧边栏模式(不含主导航)" placement="top" :show-after="500" :append-to-body="false">
<div class="mode mode-single" :class="{'active': menuMode === 'single'}" @click="menuMode = 'single'">
<svg-icon name="el-icon-check" />
</div>
</el-tooltip>
</div>
<el-divider>侧边栏</el-divider>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">折叠按钮</div>
<el-switch v-model="enableSidebarCollapse" />
</div>
<div class="setting-item">
<div class="label">是否折叠</div>
<el-switch v-model="sidebarCollapse" />
</div>
<div class="setting-item">
<div class="label">
切换跳转
<el-tooltip content="开启该功能后,切换侧边栏时,页面自动跳转至该侧边栏导航下第一个路由地址" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="switchSidebarAndPageJump" :disabled="['single'].includes(settingsStore.menuMode)" />
</div>
<div class="setting-item">
<div class="label">
保持展开一个
<el-tooltip content="开启该功能后,侧边栏只保持一个子菜单的展开" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="sidebarUniqueOpened" />
</div>
<el-divider>顶栏</el-divider>
<div class="setting-item">
<div class="label">顶栏固定</div>
<el-switch v-model="topbarFixed" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">面包屑导航</div>
<el-switch v-model="enableBreadcrumb" />
</div>
<el-divider>功能按钮</el-divider>
<div class="setting-item">
<div class="label">
导航栏搜索
<el-tooltip content="对导航栏进行快捷搜索" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="enableNavSearch" />
</div>
<div v-if="settingsStore.mode === 'pc'" class="setting-item">
<div class="label">
全屏
<el-tooltip content="该功能使用场景极少,用户习惯于通过窗口“最大化”功能来扩大显示区域,以显示更多内容,并且使用 F11 键也可以进入全屏效果" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="enableFullscreen" />
</div>
<div class="setting-item">
<div class="label">
页面刷新
<el-tooltip content="开启时会阻止原生 F5 键刷新功能,并采用框架提供的刷新模式进行页面刷新" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="enablePageReload" />
</div>
<el-divider>其它</el-divider>
<div class="setting-item">
<div class="label">
组件尺寸
<el-tooltip content="全局设置 Element Plus 组件的默认尺寸大小" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-radio-group v-model="elementSize" size="small">
<el-radio-button label="large">较大</el-radio-button>
<el-radio-button label="default">默认</el-radio-button>
<el-radio-button label="small">稍小</el-radio-button>
</el-radio-group>
</div>
<div class="setting-item">
<div class="label">底部版权</div>
<el-switch v-model="showCopyright" />
</div>
<div class="setting-item">
<div class="label">
加载进度条
<el-tooltip content="该功能开启时,跳转路由会看到页面顶部有条蓝色的进度条" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="enableProgress" />
</div>
<div class="setting-item">
<div class="label">
动态标题
<el-tooltip content="该功能开启时,页面标题会显示当前路由标题,格式为“页面标题 - 网站名称”;关闭时则显示网站名称,网站名称在项目根目录下 .env.* 文件里配置" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="enableDynamicTitle" />
</div>
<div class="setting-item">
<div class="label">
欢迎页
<el-tooltip content="欢迎页即控制台页面,该功能开启时,登录成功默认进入控制台页面;关闭时则默认进入侧边栏导航第一个导航页面" placement="top" :append-to-body="false">
<svg-icon name="el-icon-question-filled" />
</el-tooltip>
</div>
<el-switch v-model="enableDashboard" />
</div>
</el-drawer>
</teleport>
</template>
<script setup>
const { proxy } = getCurrentInstance()
const route = useRoute()
import { useSettingsStore } from '@/store/modules/settings'
const settingsStore = useSettingsStore()
import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const isShow = ref(false)
const menuMode = computed({
get: function() {
return settingsStore.menuMode
},
set: function(newValue) {
menuStore.switchHeaderActived(0)
settingsStore.updateThemeSetting({
'menuMode': newValue
})
settingsStore.menuMode !== 'single' && menuStore.setHeaderActived(route.fullPath)
}
})
const elementSize = computed({
get: function() {
return settingsStore.elementSize
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'elementSize': newValue
})
}
})
const enableSidebarCollapse = computed({
get: function() {
return settingsStore.enableSidebarCollapse
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enableSidebarCollapse': newValue
})
}
})
const sidebarCollapse = computed({
get: function() {
return settingsStore.sidebarCollapse
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'sidebarCollapse': newValue
})
}
})
const switchSidebarAndPageJump = computed({
get: function() {
return settingsStore.switchSidebarAndPageJump
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'switchSidebarAndPageJump': newValue
})
}
})
const sidebarUniqueOpened = computed({
get: function() {
return settingsStore.sidebarUniqueOpened
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'sidebarUniqueOpened': newValue
})
}
})
const topbarFixed = computed({
get: function() {
return settingsStore.topbarFixed
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'topbarFixed': newValue
})
}
})
const enableBreadcrumb = computed({
get: function() {
return settingsStore.enableBreadcrumb
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enableBreadcrumb': newValue
})
}
})
const showCopyright = computed({
get: function() {
return settingsStore.showCopyright
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'showCopyright': newValue
})
}
})
const enableNavSearch = computed({
get: function() {
return settingsStore.enableNavSearch
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enableNavSearch': newValue
})
}
})
const enableFullscreen = computed({
get: function() {
return settingsStore.enableFullscreen
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enableFullscreen': newValue
})
}
})
const enablePageReload = computed({
get: function() {
return settingsStore.enablePageReload
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enablePageReload': newValue
})
}
})
const enableProgress = computed({
get: function() {
return settingsStore.enableProgress
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enableProgress': newValue
})
}
})
const enableDynamicTitle = computed({
get: function() {
return settingsStore.enableDynamicTitle
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enableDynamicTitle': newValue
})
}
})
const enableDashboard = computed({
get: function() {
return settingsStore.enableDashboard
},
set: function(newValue) {
settingsStore.updateThemeSetting({
'enableDashboard': newValue
})
}
})
onMounted(() => {
proxy.$eventBus.on('global-theme-toggle', () => {
isShow.value = !isShow.value
})
})
</script>
<style lang="scss" scoped>
.menu-mode {
display: flex;
align-items: center;
justify-content: space-around;
padding-bottom: 10px;
.mode {
position: relative;
width: 50px;
height: 40px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
background-color: #e1e3e6;
box-shadow: 0 0 15px 1px #aaa;
transition: 0.3s;
&:hover {
box-shadow: 0 0 15px 1px #666;
}
&.active {
box-shadow: 0 0 0 2px #409eff;
}
&::before,
&::after {
pointer-events: none;
}
&-side {
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 10px;
height: 100%;
background-color: #222b45;
}
&::after {
content: "";
position: absolute;
top: 0;
left: 10px;
width: 15px;
height: 100%;
background-color: #fff;
}
}
&-head {
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 10px;
background-color: #222b45;
}
&::after {
content: "";
position: absolute;
top: 10px;
left: 0;
width: 15px;
height: calc(100% - 10px);
background-color: #fff;
}
}
&-single {
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 15px;
height: 100%;
background-color: #fff;
}
}
i {
position: absolute;
right: 2px;
bottom: 2px;
display: none;
}
&.active i {
display: block;
color: #409eff;
}
}
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 5px 0;
padding: 5px 10px;
border-radius: 5px;
transition: all 0.3s;
&:hover {
background: #f1f1f1;
}
.label {
font-size: 14px;
color: #666;
display: flex;
align-items: center;
i {
margin-left: 4px;
font-size: 17px;
color: #e6a23c;
cursor: help;
}
}
.el-switch {
height: auto;
}
}
</style>

View File

@ -5,13 +5,13 @@
<svg-icon name="pro" />
<span class="title">查看专业版</span>
</span>
<span v-if="settingsStore.enableNavSearch" class="item" @click="$eventBus.emit('global-search-toggle')">
<span v-if="settingsStore.topbar.enableNavSearch" class="item" @click="$eventBus.emit('global-search-toggle')">
<svg-icon name="search" />
</span>
<span v-if="settingsStore.mode === 'pc' && settingsStore.enableFullscreen" class="item" @click="toggle">
<span v-if="settingsStore.mode === 'pc' && settingsStore.topbar.enableFullscreen" class="item" @click="toggle">
<svg-icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" />
</span>
<span v-if="settingsStore.enablePageReload" class="item" @click="reload()">
<span v-if="settingsStore.topbar.enablePageReload" class="item" @click="reload()">
<svg-icon name="toolbar-reload" />
</span>
<span v-if="settingsStore.enableThemeSetting" class="item" @click="$eventBus.emit('global-theme-toggle')">
@ -28,7 +28,7 @@
</div>
<template #dropdown>
<el-dropdown-menu class="user-dropdown">
<el-dropdown-item v-if="settingsStore.enableDashboard" command="dashboard">控制台</el-dropdown-item>
<el-dropdown-item v-if="settingsStore.dashboard.enable" command="dashboard">控制台</el-dropdown-item>
<el-dropdown-item command="setting">个人设置</el-dropdown-item>
<el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>

View File

@ -1,15 +1,15 @@
<template>
<div
class="topbar-container" :class="{
'fixed': settingsStore.topbarFixed,
'fixed': settingsStore.topbar.fixed,
'shadow': scrollTop
}" data-fixed-calc-width
>
<div class="left-box">
<div v-if="enableSidebarCollapse" class="sidebar-collapse" :class="{'is-collapse': settingsStore.sidebarCollapse}" @click="settingsStore.toggleSidebarCollapse()">
<div v-if="enableSidebarCollapse" class="sidebar-collapse" :class="{'is-collapse': settingsStore.menu.subMenuCollapse}" @click="settingsStore.toggleSidebarCollapse()">
<svg-icon name="toolbar-collapse" />
</div>
<el-breadcrumb v-if="settingsStore.enableBreadcrumb && settingsStore.mode === 'pc'" separator-class="el-icon-arrow-right">
<el-breadcrumb v-if="settingsStore.topbar.enableBreadcrumb && settingsStore.mode === 'pc'" separator-class="el-icon-arrow-right">
<transition-group name="breadcrumb">
<template v-for="(item, index) in breadcrumbList">
<el-breadcrumb-item v-if="index < breadcrumbList.length - 1" :key="item.path" :to="pathCompile(item.path)">
@ -38,17 +38,17 @@ const settingsStore = useSettingsStore()
const enableSidebarCollapse = computed(() => {
return settingsStore.mode === 'mobile' || (
['side', 'head', 'single'].includes(settingsStore.menuMode) &&
settingsStore.enableSidebarCollapse
['side', 'head', 'single'].includes(settingsStore.menu.menuMode) &&
settingsStore.topbar.enableSidebarCollapse
)
})
const breadcrumbList = computed(() => {
let breadcrumbList = []
if (settingsStore.enableDashboard) {
if (settingsStore.dashboard.enable) {
breadcrumbList.push({
path: '/dashboard',
title: settingsStore.dashboardTitle
title: settingsStore.dashboard.title
})
}
if (route.meta.breadcrumbNeste) {

View File

@ -3,13 +3,13 @@
<div id="app-main">
<Header />
<div class="wrapper">
<div class="sidebar-container" :class="{'show': settingsStore.mode === 'mobile' && !settingsStore.sidebarCollapse}">
<div class="sidebar-container" :class="{'show': settingsStore.mode === 'mobile' && !settingsStore.menu.subMenuCollapse}">
<MainSidebar />
<SubSidebar />
</div>
<div class="sidebar-mask" :class="{'show': settingsStore.mode === 'mobile' && !settingsStore.sidebarCollapse}" @click="settingsStore.toggleSidebarCollapse()" />
<div class="sidebar-mask" :class="{'show': settingsStore.mode === 'mobile' && !settingsStore.menu.subMenuCollapse}" @click="settingsStore.toggleSidebarCollapse()" />
<div class="main-container" :style="{'padding-bottom': $route.meta.paddingBottom}">
<Topbar v-if="!(settingsStore.menuMode === 'head' && !settingsStore.enableSidebarCollapse && !settingsStore.enableBreadcrumb)" />
<Topbar v-if="!(settingsStore.menu.menuMode === 'head' && !settingsStore.topbar.enableSidebarCollapse && !settingsStore.topbar.enableBreadcrumb)" />
<div class="main">
<router-view v-slot="{ Component, route }">
<transition name="main" mode="out-in" appear>
@ -26,7 +26,7 @@
<el-backtop :right="20" :bottom="20" title="回到顶部" />
</div>
<Search />
<ThemeSetting />
<AppSetting />
<BuyIt />
</div>
</template>
@ -37,7 +37,7 @@ import MainSidebar from './components/MainSidebar/index.vue'
import SubSidebar from './components/SubSidebar/index.vue'
import Topbar from './components/Topbar/index.vue'
import Search from './components/Search/index.vue'
import ThemeSetting from './components/ThemeSetting/index.vue'
import AppSetting from './components/AppSetting/index.vue'
import BuyIt from './components/BuyIt/index.vue'
import { isExternalLink } from '@/util'
@ -52,10 +52,10 @@ import { useMenuStore } from '@/store/modules/menu'
const menuStore = useMenuStore()
const showCopyright = computed(() => {
return typeof routeInfo.meta.copyright !== 'undefined' ? routeInfo.meta.copyright : settingsStore.showCopyright
return typeof routeInfo.meta.copyright !== 'undefined' ? routeInfo.meta.copyright : settingsStore.copyright.enable
})
watch(() => settingsStore.sidebarCollapse, val => {
watch(() => settingsStore.menu.subMenuCollapse, val => {
if (settingsStore.mode === 'mobile') {
if (!val) {
document.querySelector('body').classList.add('hidden')
@ -67,15 +67,15 @@ watch(() => settingsStore.sidebarCollapse, val => {
watch(() => routeInfo.path, () => {
if (settingsStore.mode === 'mobile') {
settingsStore.updateThemeSetting({
sidebarCollapse: true
settingsStore.$patch(state => {
state.menu.subMenuCollapse = true
})
}
})
onMounted(() => {
proxy.$hotkeys('f5', e => {
if (settingsStore.enablePageReload) {
if (settingsStore.topbar.enablePageReload) {
e.preventDefault()
reload()
}
@ -91,7 +91,7 @@ function reload() {
provide('switchMenu', switchMenu)
function switchMenu(index) {
menuStore.switchHeaderActived(index)
if (settingsStore.switchSidebarAndPageJump) {
if (settingsStore.menu.switchMainMenuAndPageJump) {
if (isExternalLink(menuStore.sidebarRoutesFirstDeepestPath)) {
window.open(menuStore.sidebarRoutesFirstDeepestPath, '_blank')
} else {

View File

@ -19,7 +19,7 @@ for (var key in ElementIcons) {
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus, {
locale: zhCn,
size: useSettingsOutsideStore().elementSize
size: useSettingsOutsideStore().app.elementSize
})
import globalProperties from '@/util/global.properties'

View File

@ -30,7 +30,7 @@ const constantRoutes = [
meta: {
title: () => {
const settingsOutsideStore = useSettingsOutsideStore()
return settingsOutsideStore.dashboardTitle
return settingsOutsideStore.dashboard.title
}
}
},
@ -142,13 +142,13 @@ router.beforeEach(async(to, from, next) => {
const settingsOutsideStore = useSettingsOutsideStore()
const userOutsideStore = useUserOutsideStore()
const menuOutsideStore = useMenuOutsideStore()
settingsOutsideStore.enableProgress && (isLoading.value = true)
settingsOutsideStore.app.enableProgress && (isLoading.value = true)
// 是否已登录
if (userOutsideStore.isLogin) {
// 是否已根据权限动态生成并挂载路由
if (menuOutsideStore.isGenerate) {
// 导航栏如果不是 single 模式,则需要根据 path 定位主导航的选中状态
settingsOutsideStore.menuMode !== 'single' && menuOutsideStore.setHeaderActived(to.path)
settingsOutsideStore.menu.menuMode !== 'single' && menuOutsideStore.setHeaderActived(to.path)
if (to.name) {
if (to.matched.length !== 0) {
// 如果已登录状态下,进入登录页会强制跳转到控制台页面
@ -157,7 +157,7 @@ router.beforeEach(async(to, from, next) => {
name: 'dashboard',
replace: true
})
} else if (!settingsOutsideStore.enableDashboard && to.name == 'dashboard') {
} else if (!settingsOutsideStore.dashboard.enable && to.name == 'dashboard') {
// 如果未开启控制台页面,则默认进入侧边栏导航第一个模块
if (menuOutsideStore.sidebarRoutes.length > 0) {
next({
@ -181,7 +181,7 @@ router.beforeEach(async(to, from, next) => {
}
} else {
let accessRoutes = []
if (!settingsOutsideStore.enableBackendReturnRoute) {
if (!settingsOutsideStore.app.enableBackendReturnRoute) {
accessRoutes = await menuOutsideStore.generateRoutesAtFront(asyncRoutes)
} else {
accessRoutes = await menuOutsideStore.generateRoutesAtBack()
@ -214,7 +214,7 @@ router.beforeEach(async(to, from, next) => {
router.afterEach((to, from) => {
const settingsOutsideStore = useSettingsOutsideStore()
const keepAliveOutsideStore = useKeepAliveOutsideStore()
settingsOutsideStore.enableProgress && (isLoading.value = false)
settingsOutsideStore.app.enableProgress && (isLoading.value = false)
// 设置页面 title
to.meta.title && settingsOutsideStore.setTitle(typeof to.meta.title === 'function' ? to.meta.title() : to.meta.title)
// 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态

1
src/settings.custom.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -1,68 +1,88 @@
let globalSettings = {
/**
* 是否开启权限功能权限功能提供以下鉴权支持
* 1路由鉴权
* 2鉴权组件<Auth></Auth><AuthAll></AuthAll>
* 3鉴权指令v-authv-auth-all
* 4鉴权函数this.$auth()this.$authAll()
*/
enablePermission: false,
/**
* 导航栏模式
* side 侧边栏模式含主导航
* head 顶部模式
* single 侧边栏模式无主导航
*/
menuMode: 'side',
// Element 组件默认尺寸支持large、default、small
elementSize: 'default',
// 是否开启侧边栏展开收起按钮
enableSidebarCollapse: false,
// 侧边栏是否收起
sidebarCollapse: false,
// 切换侧边栏同时跳转页面
switchSidebarAndPageJump: false,
// 侧边栏只保持一个子菜单的展开
sidebarUniqueOpened: true,
// 顶栏是否固定
topbarFixed: true,
// 是否开启面包屑导航
enableBreadcrumb: true,
// 是否显示底部版权信息,同时在路由 meta 对象里可以单独设置某个路由是否显示底部版权信息
showCopyright: true,
// 版权信息配置格式为Copyright © [dates] <company>, All Rights Reserved
copyrightDates: '2020-2021',
copyrightCompany: 'Fantastic-admin',
copyrightWebsite: 'https://hooray.github.io/fantastic-admin',
// 是否开启导航搜索
enableNavSearch: true,
// 是否开启移动端适配,开启后当页面宽度小于 992px 时自动切换为移动端展示
enableMobileAdaptation: true,
// 是否开启全屏
enableFullscreen: false,
// 是否开启页面刷新
enablePageReload: false,
// 是否开启载入进度条
enableProgress: true,
// 是否开启动态标题
enableDynamicTitle: false,
// 是否开启控制台
enableDashboard: true,
// 控制台名称
dashboardTitle: '控制台',
// 是否开启后端返回路由数据
enableBackendReturnRoute: false,
app: {
// Element 组件默认尺寸支持large、default、small
elementSize: 'default',
/**
* 是否开启权限功能权限功能提供以下鉴权支持
* 1路由鉴权
* 2鉴权组件<Auth></Auth><AuthAll></AuthAll>
* 3鉴权指令v-authv-auth-all
* 4鉴权函数this.$auth()this.$authAll()
*/
enablePermission: false,
// 是否开启后端返回路由数据
enableBackendReturnRoute: false,
// 是否开启载入进度条
enableProgress: true,
// 是否开启动态标题
enableDynamicTitle: false
},
// 控制台
dashboard: {
// 是否开启
enable: true,
// 控制台名称
title: '控制台'
},
// 布局
layout: {
// 是否开启移动端适配,开启后当页面宽度小于 992px 时自动切换为移动端展示
enableMobileAdaptation: true
},
// 导航栏
menu: {
/**
* 导航栏模式
* side 侧边栏模式含主导航
* head 顶部模式
* single 侧边栏模式无主导航
*/
menuMode: 'side',
// 切换主导航同时跳转页面
switchMainMenuAndPageJump: false,
// 次导航只保持一个子项的展开
subMenuUniqueOpened: true,
// 次导航是否收起
subMenuCollapse: false
},
// 顶栏
topbar: {
// 是否固定
fixed: true,
// 是否开启侧边栏展开收起按钮
enableSidebarCollapse: false,
// 是否开启面包屑导航
enableBreadcrumb: true,
// 是否开启导航搜索
enableNavSearch: true,
// 是否开启全屏
enableFullscreen: false,
// 是否开启页面刷新
enablePageReload: false
},
// 底部版权
copyright: {
// 是否开启,同时在路由 meta 对象里可以单独设置某个路由是否显示底部版权信息
enable: true,
// 版权信息配置格式为Copyright © [dates] <company>, All Rights Reserved
dates: '2020-2022',
company: 'Fantastic-admin',
website: 'https://hooray.github.io/fantastic-admin'
},
// 是否开启主题配置(建议在生产环境关闭)
enableThemeSetting: true
}
import settingsCustom from './settings.custom.json'
Object.assign(globalSettings, settingsCustom)
// 演示&开发环境开启全部功能(这部分代码可删除,仅方便作者打包演示环境)
if (import.meta.env.VITE_APP_MODE === 'example' || import.meta.env.MODE === 'development') {
globalSettings.enablePermission = true
globalSettings.enableSidebarCollapse = true
globalSettings.enableFullscreen = true
globalSettings.enablePageReload = true
globalSettings.enableDynamicTitle = true
globalSettings.app.enablePermission = true
globalSettings.app.enableDynamicTitle = true
globalSettings.topbar.enableSidebarCollapse = true
globalSettings.topbar.enableFullscreen = true
globalSettings.topbar.enablePageReload = true
}
export default globalSettings

View File

@ -157,7 +157,7 @@ export const useMenuStore = defineStore(
transformRoutes: state => {
let routes
const settingsStore = useSettingsStore()
if (settingsStore.menuMode === 'single') {
if (settingsStore.menu.menuMode === 'single') {
routes = [{ children: [] }]
state.routes.map(item => {
routes[0].children.push(...item.children)
@ -183,7 +183,7 @@ export const useMenuStore = defineStore(
const userStore = useUserStore()
let accessedRoutes
// 如果权限功能开启,则需要对路由数据进行筛选过滤
if (settingsStore.enablePermission) {
if (settingsStore.app.enablePermission) {
const permissions = await userStore.getPermissions()
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
} else {
@ -231,7 +231,7 @@ export const useMenuStore = defineStore(
let asyncRoutes = formatBackRoutes(res.data)
let accessedRoutes
// 如果权限功能开启,则需要对路由数据进行筛选过滤
if (settingsStore.enablePermission) {
if (settingsStore.app.enablePermission) {
const permissions = await userStore.getPermissions()
accessedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
} else {

View File

@ -9,7 +9,7 @@ export const useSettingsStore = defineStore(
state: () => ({
...settings,
// 侧边栏是否收起(用于记录 pc 模式下最后的状态)
sidebarCollapseLastStatus: settings.sidebarCollapse,
subMenuCollapseLastStatus: settings.menu.subMenuCollapse,
// 显示模式支持mobile、pc
mode: 'pc',
// 页面标题
@ -18,7 +18,7 @@ export const useSettingsStore = defineStore(
actions: {
// 设置访问模式
setMode(width) {
if (this.enableMobileAdaptation) {
if (this.layout.enableMobileAdaptation) {
// 先判断 UA 是否为移动端设备(手机&平板)
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
this.mode = 'mobile'
@ -40,9 +40,9 @@ export const useSettingsStore = defineStore(
},
// 切换侧边栏导航展开/收起
toggleSidebarCollapse() {
this.sidebarCollapse = !this.sidebarCollapse
this.menu.subMenuCollapse = !this.menu.subMenuCollapse
if (this.mode == 'pc') {
this.sidebarCollapseLastStatus = !this.sidebarCollapseLastStatus
this.subMenuCollapseLastStatus = !this.subMenuCollapseLastStatus
}
},
// 更新主题配置

View File

@ -38,7 +38,7 @@ export function deepClone(target) {
function hasPermission(permission) {
const settingsOutsideStore = useSettingsOutsideStore()
const userOutsideStore = useUserOutsideStore()
if (settingsOutsideStore.enablePermission) {
if (settingsOutsideStore.app.enablePermission) {
return userOutsideStore.permissions.some(v => {
return v === permission
})

View File

@ -80,7 +80,7 @@
</el-row>
</el-form>
</div>
<Copyright v-if="settingsStore.showCopyright" />
<Copyright v-if="settingsStore.copyright.enable" />
</div>
</template>

View File

@ -2,7 +2,7 @@
<div>
<page-header title="权限验证" />
<page-main>
<div v-if="!settingsStore.enablePermission">请到 seeting.js 里打开权限功能再进入该页面查看演示</div>
<div v-if="!settingsStore.app.enablePermission">请到 seeting.js 里打开权限功能再进入该页面查看演示</div>
<div v-else>
<h3>切换帐号</h3>
<el-radio-group v-model="userStore.account" @change="accountChange">