Commit e17762bd by 赵月辉

新增控制台系统需要的框架组件

parent 6b9b10fa
...@@ -16,7 +16,7 @@ $base-logo-title-color: #ffffff; ...@@ -16,7 +16,7 @@ $base-logo-title-color: #ffffff;
$base-menu-light-color: rgba(0, 0, 0, 0.7); $base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background: #ffffff; $base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529; $base-logo-light-title-color: #2E77E3;;
$base-sub-menu-background: #1f2d3d; $base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528; $base-sub-menu-hover: #001528;
...@@ -42,7 +42,7 @@ $--color-warning: #E6A23C; ...@@ -42,7 +42,7 @@ $--color-warning: #E6A23C;
$--color-danger: #F56C6C; $--color-danger: #F56C6C;
$--color-info: #909399; $--color-info: #909399;
$base-sidebar-width: 200px; $base-sidebar-width: 300px;
// the :export directive is the magic sauce for webpack // the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
......
<template> <template>
<div class="navbar"> <div class="navbar">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" /> @toggleClick="toggleSideBar"/>
<top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" /> <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav && !hideBreadcrumb"/>
<top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav"/>
<div class="right-menu"> <div class="right-menu">
<template v-if="appStore.device !== 'mobile'"> <template v-if="appStore.device !== 'mobile'">
<header-search id="header-search" class="right-menu-item" /> <!-- <header-search id="header-search" class="right-menu-item"/>-->
<el-tooltip content="源码地址" effect="dark" placement="bottom"> <!-- <el-tooltip content="源码地址" effect="dark" placement="bottom">-->
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" /> <!-- <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect"/>-->
</el-tooltip> <!-- </el-tooltip>-->
<el-tooltip content="文档地址" effect="dark" placement="bottom"> <!-- <el-tooltip content="文档地址" effect="dark" placement="bottom">-->
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" /> <!-- <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect"/>-->
</el-tooltip> <!-- </el-tooltip>-->
<screenfull id="screenfull" class="right-menu-item hover-effect" /> <screenfull id="screenfull" class="right-menu-item hover-effect"/>
<el-tooltip content="布局大小" effect="dark" placement="bottom"> <el-tooltip content="布局大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" /> <size-select id="size-select" class="right-menu-item hover-effect"/>
</el-tooltip> </el-tooltip>
</template> </template>
<div class="avatar-container"> <div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click"> <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" /> <img :src="userStore.avatar" class="user-avatar"/>
<el-icon><caret-bottom /></el-icon> <el-icon>
<caret-bottom/>
</el-icon>
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
...@@ -65,38 +68,47 @@ const appStore = useAppStore() ...@@ -65,38 +68,47 @@ const appStore = useAppStore()
const userStore = useUserStore() const userStore = useUserStore()
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
function toggleSideBar() { defineProps({
hideBreadcrumb: {
type: Boolean,
default: false
}
})
function toggleSideBar () {
appStore.toggleSideBar() appStore.toggleSideBar()
} }
function handleCommand(command) { function handleCommand (command) {
switch (command) { switch (command) {
case "setLayout": case 'setLayout':
setLayout(); setLayout()
break; break
case "logout": case 'logout':
logout(); logout()
break; break
default: default:
break; break
} }
} }
function logout() { function logout () {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', { ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { }).then(() => {
userStore.logOut().then(() => { userStore.logOut().then(() => {
location.href = '/index'; location.href = '/index'
})
}).catch(() => {
}) })
}).catch(() => { });
} }
const emits = defineEmits(['setLayout']) const emits = defineEmits(['setLayout'])
function setLayout() {
emits('setLayout'); function setLayout () {
emits('setLayout')
} }
</script> </script>
......
<template>
<component :is="type" v-bind="linkProps()">
<slot />
</component>
</template>
<script setup>
import { isExternal } from '@/utils/validate'
const props = defineProps({
to: {
type: [String, Object],
required: true
}
})
const isExt = computed(() => {
return isExternal(props.to)
})
const type = computed(() => {
if (isExt.value) {
return 'a'
}
return 'router-link'
})
function linkProps() {
if (isExt.value) {
return {
href: props.to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: props.to
}
}
</script>
<template>
<div class="sidebar-logo-container" :class="{ 'collapse': collapse }" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="xjjtLogo" :src="xjjtLogo" class="sidebar-logo" />
<img v-if="nsccLogo" :src="nsccLogo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="xjjtLogo" :src="xjjtLogo" class="sidebar-logo" />
<img v-if="nsccLogo" :src="nsccLogo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }}</h1>
</router-link>
</transition>
</div>
</template>
<script setup>
import variables from '@/assets/styles/variables.module.scss'
// import logo from '@/assets/logo/logo.png'
import xjjtLogo from '@/assets/logo/xjjt-logo.png'
import nsccLogo from '@/assets/logo/nscc-logo.jpg'
import useSettingsStore from '@/store/modules/settings'
defineProps({
collapse: {
type: Boolean,
required: true
}
})
const title = import.meta.env.VITE_APP_TITLE;
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
& .sidebar-logo-link {
height: 100%;
width: 100%;
& .sidebar-logo {
width: 32px;
height: 32px;
vertical-align: middle;
margin-right: 12px;
}
& .sidebar-title {
font-family: YouSheBiaoTiHei, system-ui;
display: inline-block;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
font-size: 20px;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/>
<template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template>
</el-menu-item>
</app-link>
</template>
<el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
<template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta && item.meta.icon" />
<span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span>
</template>
<sidebar-item
v-for="(child, index) in item.children"
:key="child.path + index"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-sub-menu>
</div>
</template>
<script setup>
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/ruoyi'
const props = defineProps({
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
}
})
const onlyOneChild = ref({});
function hasOneShowingChild(children = [], parent) {
if (!children) {
children = [];
}
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
};
function resolvePath(routePath, routeQuery) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(props.basePath)) {
return props.basePath
}
if (routeQuery) {
let query = JSON.parse(routeQuery);
return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
}
return getNormalPath(props.basePath + '/' + routePath)
}
function hasTitle(title){
if (title.length > 5) {
return title;
} else {
return "";
}
}
</script>
<template>
<div :class="{ 'has-logo': showLogo }"
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
<logo v-if="showLogo" :collapse="isCollapse"/>
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
<div>
<div class="title" v-if="!isCollapse">控制台</div>
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
:text-color="sideTheme === 'theme-dark' ? variables.menuColor : variables.menuLightColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="vertical">
<sidebar-item
v-for="(route, index) in sidebarRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-menu>
</div>
</el-scrollbar>
</div>
</template>
<script setup>
import Logo from './Logo'
import SidebarItem from './SidebarItem'
import variables from '@/assets/styles/variables.module.scss'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
const route = useRoute()
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => {
return permissionStore.sidebarRouters.filter(item => item.path === '/console') // 只展示控制台的菜单项
})
const showLogo = computed(() => settingsStore.sidebarLogo)
const sideTheme = computed(() => settingsStore.sideTheme)
const theme = computed(() => settingsStore.theme)
const isCollapse = computed(() => !appStore.sidebar.opened)
const activeMenu = computed(() => {
const {meta, path} = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
</script>
<style scoped lang="scss">
.title {
font-weight: bold;
font-size: 18px;
color: #303233;
padding: 20px;
}
</style>
<template> <template>
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }"> <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/> <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
<sidebar v-if="!sidebar.hide" class="sidebar-container" /> <sidebar-custom v-if="!sidebar.hide" class="sidebar-container"/>
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container"> <div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
<div :class="{ 'fixed-header': fixedHeader }"> <div :class="{ 'fixed-header': fixedHeader }">
<navbar @setLayout="setLayout" /> <navbar :hide-breadcrumb="true" @setLayout="setLayout"/>
<!-- <tags-view v-if="needTagsView" />--> <!--<tags-view v-if="needTagsView" />-->
</div> </div>
<app-main /> <app-main/>
<settings ref="settingRef" /> <settings ref="settingRef"/>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { useWindowSize } from '@vueuse/core' import { useWindowSize } from '@vueuse/core'
import Sidebar from './components/Sidebar/index.vue' import SidebarCustom from './components/Sidebar-custom/index.vue'
import { AppMain, Navbar, Settings, TagsView } from './components' import { AppMain, Navbar, Settings, TagsView } from './components'
import defaultSettings from '@/settings' import defaultSettings from '@/settings'
...@@ -23,12 +23,12 @@ import useAppStore from '@/store/modules/app' ...@@ -23,12 +23,12 @@ import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings' import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore() const settingsStore = useSettingsStore()
const theme = computed(() => settingsStore.theme); const theme = computed(() => settingsStore.theme)
const sideTheme = computed(() => settingsStore.sideTheme); const sideTheme = computed(() => settingsStore.sideTheme)
const sidebar = computed(() => useAppStore().sidebar); const sidebar = computed(() => useAppStore().sidebar)
const device = computed(() => useAppStore().device); const device = computed(() => useAppStore().device)
const needTagsView = computed(() => settingsStore.tagsView); const needTagsView = computed(() => settingsStore.tagsView)
const fixedHeader = computed(() => settingsStore.fixedHeader); const fixedHeader = computed(() => settingsStore.fixedHeader)
const classObj = computed(() => ({ const classObj = computed(() => ({
hideSidebar: !sidebar.value.opened, hideSidebar: !sidebar.value.opened,
...@@ -37,34 +37,35 @@ const classObj = computed(() => ({ ...@@ -37,34 +37,35 @@ const classObj = computed(() => ({
mobile: device.value === 'mobile' mobile: device.value === 'mobile'
})) }))
const { width, height } = useWindowSize(); const {width, height} = useWindowSize()
const WIDTH = 992; // refer to Bootstrap's responsive design const WIDTH = 992 // refer to Bootstrap's responsive design
watchEffect(() => { watchEffect(() => {
if (device.value === 'mobile' && sidebar.value.opened) { if (device.value === 'mobile' && sidebar.value.opened) {
useAppStore().closeSideBar({ withoutAnimation: false }) useAppStore().closeSideBar({withoutAnimation: false})
} }
if (width.value - 1 < WIDTH) { if (width.value - 1 < WIDTH) {
useAppStore().toggleDevice('mobile') useAppStore().toggleDevice('mobile')
useAppStore().closeSideBar({ withoutAnimation: true }) useAppStore().closeSideBar({withoutAnimation: true})
} else { } else {
useAppStore().toggleDevice('desktop') useAppStore().toggleDevice('desktop')
} }
}) })
function handleClickOutside() { function handleClickOutside () {
useAppStore().closeSideBar({ withoutAnimation: false }) useAppStore().closeSideBar({withoutAnimation: false})
} }
const settingRef = ref(null); const settingRef = ref(null)
function setLayout() {
settingRef.value.openSetting(); function setLayout () {
settingRef.value.openSetting()
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@/assets/styles/mixin.scss"; @import "@/assets/styles/mixin.scss";
@import "@/assets/styles/variables.module.scss"; @import "@/assets/styles/variables.module.scss";
.app-wrapper { .app-wrapper {
@include clearfix; @include clearfix;
...@@ -78,6 +79,41 @@ function setLayout() { ...@@ -78,6 +79,41 @@ function setLayout() {
} }
} }
.sidebar-container {
box-shadow: none !important;
:deep(.el-menu) {
--el-menu-item-font-size: 16px;
--el-menu-text-color: #303233;
.el-menu-item {
position: relative;
font-weight: 400;
&.is-active {
background-color: #F0F6FF;
&:before {
content: '';
position: absolute;
top: 0;
right: 0;
height: 100%;
border-right: 4px solid #2e77e3;
}
}
.svg-icon{
font-size: 18px;
}
}
}
}
.app-main {
background-color: #F0F2F5;
}
.drawer-bg { .drawer-bg {
background: #000; background: #000;
opacity: 0.3; opacity: 0.3;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment