初始化项目

This commit is contained in:
zx
2026-03-09 15:02:47 +08:00
commit 97b070a1a4
274 changed files with 24365 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
<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>

View File

@@ -0,0 +1,102 @@
<template>
<div class="sidebar-logo-container" :class="{ 'collapse': collapse }">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title">{{ title }}</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title">{{ title }}</h1>
</router-link>
</transition>
</div>
</template>
<script setup>
import logo from '@/assets/logo/logo.png'
import useSettingsStore from '@/store/modules/settings'
import variables from '@/assets/styles/variables.module.scss'
defineProps({
collapse: {
type: Boolean,
required: true
}
})
const title = import.meta.env.VITE_APP_TITLE
const settingsStore = useSettingsStore()
const sideTheme = computed(() => settingsStore.sideTheme)
// 获取Logo背景色
const getLogoBackground = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-bg)'
}
if (settingsStore.navType == 3) {
return variables.menuLightBg
}
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
})
// 获取Logo文字颜色
const getLogoTextColor = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-text)'
}
if (settingsStore.navType == 3) {
return variables.menuLightText
}
return sideTheme.value === 'theme-dark' ? '#fff' : variables.menuLightText
})
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-container {
position: relative;
height: 50px;
line-height: 50px;
background: v-bind(getLogoBackground);
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 {
display: inline-block;
margin: 0;
color: v-bind(getLogoTextColor);
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}
&.collapse {
.sidebar-logo {
margin-right: 0px;
}
}
}
</style>

View File

@@ -0,0 +1,100 @@
<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
}
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>

View File

@@ -0,0 +1,104 @@
<template>
<div :class="{ 'has-logo': showLogo }" class="sidebar-container">
<logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="getMenuBackground"
:text-color="getMenuTextColor"
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
mode="vertical"
:class="sideTheme"
>
<sidebar-item
v-for="(route, index) in sidebarRouters"
:key="route.path + index"
:item="route"
:base-path="route.path"
/>
</el-menu>
</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(() => permissionStore.sidebarRouters)
const showLogo = computed(() => settingsStore.sidebarLogo)
const sideTheme = computed(() => settingsStore.sideTheme)
const theme = computed(() => settingsStore.theme)
const isCollapse = computed(() => !appStore.sidebar.opened)
// 获取菜单背景色
const getMenuBackground = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-bg)'
}
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBg
})
// 获取菜单文字颜色
const getMenuTextColor = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-text)'
}
return sideTheme.value === 'theme-dark' ? variables.menuText : variables.menuLightText
})
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
</script>
<style lang="scss" scoped>
.sidebar-container {
background-color: v-bind(getMenuBackground);
.scrollbar-wrapper {
background-color: v-bind(getMenuBackground);
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
.el-menu-item, .el-sub-menu__title {
&:hover {
background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
}
}
.el-menu-item {
color: v-bind(getMenuTextColor);
&.is-active {
color: var(--menu-active-text, #409eff);
background-color: var(--menu-hover, rgba(0, 0, 0, 0.06)) !important;
}
}
.el-sub-menu__title {
color: v-bind(getMenuTextColor);
}
}
}
</style>