Conflicts:
	package-lock.json
This commit is contained in:
zouzhibing 2023-02-27 19:59:29 +08:00
commit dd5246edaf
19 changed files with 336 additions and 53 deletions

View File

@ -1,16 +1,19 @@
## 简介 ## 简介
vue-element-perfect 是一个后台前端解决方案,它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件,企业级中后台解决方案可免费商用同时支持PC、平板、手机 vue-element-perfect 是一个后台前端解决方案,它使用了最新的前端技术栈、动态路由,权限验证,并且有着丰富的组件,企业级中后台解决方案可免费商用同时支持PC、平板、手机
本项目也参考了很多开源的项目、
### 在线预览 ### 在线预览
- link —— [http://182.61.5.190:8889/ ](http://182.61.5.190:8889/)
- gitee国内访问地址https://yuanzbz.gitee.io/vue-admin-perfect/#/home - gitee国内访问地址https://yuanzbz.gitee.io/vue-admin-perfect/#/home
- github site : https://zouzhibin.github.io/vue-admin-perfect/ - github site : https://zouzhibin.github.io/vue-admin-perfect/
- 基础功能版本预览https://yuanzbz.gitee.io/vue-admin-simple
### git仓库(欢迎 Star⭐) ### git仓库(欢迎 Star⭐)
- Gitee —— [https://gitee.com/yuanzbz/vue-admin-perfect](https://gitee.com/yuanzbz/vue-admin-perfect) - Gitee —— [https://gitee.com/yuanzbz/vue-admin-perfect](https://gitee.com/yuanzbz/vue-admin-perfect)
- GitHub —— [https://github.com/zouzhibin/vue-admin-perfect](https://github.com/zouzhibin/vue-admin-perfect) - GitHub —— [https://github.com/zouzhibin/vue-admin-perfect](https://github.com/zouzhibin/vue-admin-perfect)
- 基础功能版本: —— [https://gitee.com/yuanzbz/vue-admin-perfect](https://gitee.com/yuanzbz/vue-admin-perfect)
## 项目功能 ## 项目功能
- 使用Vue3.0开发单文件组件采用script setup - 使用Vue3.0开发单文件组件采用script setup
- 采用 Vite3 作为项目开发、打包工具(配置了 Gzip 打包、TSX 语法、跨域代理) - 采用 Vite3 作为项目开发、打包工具(配置了 Gzip 打包、TSX 语法、跨域代理)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 142 KiB

View File

@ -24,6 +24,7 @@
> >
<el-option label="纵向" value="vertical"></el-option> <el-option label="纵向" value="vertical"></el-option>
<el-option label="横向" value="horizontal"></el-option> <el-option label="横向" value="horizontal"></el-option>
<el-option label="分栏" value="columns"></el-option>
</el-select> </el-select>
</div> </div>
<div class="theme-item"> <div class="theme-item">

View File

@ -0,0 +1,222 @@
<template>
<div class="main-columns">
<div class="layout-columns-aside">
<div class="logo flex-center">
<img src="@/assets/image/logo.png" alt="logo" />
</div>
<el-scrollbar>
<div class="menu-wrap">
<div
class="item-menu-wrap"
:class="{
'active-menu':activeCurrentMenu===item.path
}"
v-for="item in menusRoutes"
:key="item.path"
@click="handleChangeMenu(item)"
>
<el-icon :size="20">
<component :is="item?.meta?.icon"></component>
</el-icon>
<span class="title">{{ item?.meta?.title }}</span>
</div>
</div>
</el-scrollbar>
</div>
<div class="layout-columns-sub" :style="{ width: isCollapse ? '60px' : '210px' }">
<div class="logo flex-center">
<span v-show="subMenus.length">{{ isCollapse ? "Vue" : "Vue Admin Perfect" }}</span>
</div>
<el-scrollbar >
<el-menu
:collapse="isCollapse"
:router="false"
:default-active="activeMenu"
:unique-opened="SettingStore.themeConfig.uniqueOpened"
:collapse-transition="false"
class="menu-columns"
>
<SubMenu
:basePath="basePath"
:menuList="subMenus"
/>
</el-menu>
</el-scrollbar>
</div>
<div class="container">
<div class="layout-header">
<div class="header-tool">
<HeaderToolLeft/>
<HeaderToolRight/>
</div>
<TagsView v-if="themeConfig.showTag"/>
</div>
<Main/>
<Footer/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import {usePermissionStore} from "@/store/modules/permission"
import { useSettingStore } from "@/store/modules/setting";
import SubItem from '../components/SubMenu/SubItem.vue'
import Footer from '../components/Footer/index.vue'
import SubMenu from '../components/SubMenu/SubMenu.vue'
import TagsView from '../components/TagsView/index.vue'
const PermissionStore = usePermissionStore()
const SettingStore = useSettingStore()
const route = useRoute()
const router = useRouter();
import HeaderToolRight from '../components/Header/ToolRight.vue'
import HeaderToolLeft from '../components/Header/ToolLeft.vue'
import Main from '../components/Main/index.vue'
//
const permission_routes = computed(() => PermissionStore.permission_routes)
//
const menusRoutes = computed(()=>{
return PermissionStore.permission_routes.filter(item=>!item.hidden)
})
const activeCurrentMenu = ref('')
//
const themeConfig = computed(() =>SettingStore.themeConfig)
const isCollapse = computed(() =>!SettingStore.isCollapse)
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
const basePath = ref<string>('/')
const subMenus = ref([])
watch(()=>[route],()=>{
if (!menusRoutes.value.length) return;
const [firstMenu] = route.matched
activeCurrentMenu.value = firstMenu.path;
let menuItem = menusRoutes.value.find(item=>firstMenu.path === item.path)
if(menuItem&&menuItem.children?.length) {
subMenus.value = menuItem.children
}else {
subMenus.value = []
}
basePath.value = firstMenu.path
},{
deep: true,
immediate:true
})
const handleChangeMenu = (item)=>{
router.push(item.path);
}
</script>
<style lang="scss" scoped>
.main-columns{
display: flex;
flex-direction: row!important;
height: 100%;
width: 100%;
}
.layout-columns-aside{
flex-shrink: 0;
width: 80px;
height: 100%;
background-color: #304156;
.el-scrollbar{
height: calc(100% - 55px);
}
.logo {
box-sizing: border-box;
height: 50px;
img {
width: 32px;
object-fit: contain;
}
}
.menu-wrap{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.item-menu-wrap{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 70px;
width: 70px;
cursor: pointer;
transition: all .3s ease;
}
.active-menu{
background: #1890ff;
border-radius: 5px;
}
.title{
color: #e5eaf3;
}
.el-icon{
color: #e5eaf3;
}
}
}
.layout-columns-sub{
flex-shrink: 0;
width:200px;
box-sizing: border-box;
flex-direction: column;
overflow: hidden;
transition: all 0.3s ease;
background: white;
border-right: 1px solid #eee;
.el-scrollbar{
height: calc(100vh - 50px);
}
.logo {
width: 100%;
box-sizing: border-box;
height: 50px;
border-bottom: 1px solid #eee;
span{
font-weight: bold;
white-space: nowrap;
}
}
::v-deep(.menu-columns){
border-right: none;
}
}
.container{
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.layout-header{
background: white;
transition: width 0.28s;
flex-shrink: 0;
box-sizing: border-box;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
.header-tool{
height: 50px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
padding: 0 10px 0 0;
box-sizing: border-box;
justify-content: space-between;
}
}
</style>

View File

@ -1,33 +1,32 @@
<template> <template>
<!--纵向布局--> <!--纵向布局-->
<Height/> <Height/>
<div <div
class="m-layout-header" class="m-layout-header"
:class="{ :class="{
'fixed-header':themeConfig.fixedHeader, 'fixed-header':themeConfig.fixedHeader,
}"> }">
<div class="header-inner"> <div class="header-inner">
<el-menu <el-menu
mode="horizontal" mode="horizontal"
:default-active="activeMenu" :default-active="activeMenu"
background-color="#304156" background-color="#304156"
text-color="#bfcbd9" text-color="#bfcbd9"
:unique-opened="SettingStore.themeConfig.uniqueOpened" :unique-opened="SettingStore.themeConfig.uniqueOpened"
:collapse-transition="false" :collapse-transition="false"
class="menu-horizontal" class="menu-horizontal"
> >
<SubItem <SubItem
v-for="route in permission_routes" v-for="route in permission_routes"
:key="route.path" :key="route.path"
:item="route" :item="route"
:base-path="route.path" :base-path="route.path"
/> />
</el-menu> </el-menu>
<HeaderToolRight/> <HeaderToolRight/>
</div>
<TagsView v-if="themeConfig.showTag"/>
</div> </div>
<TagsView v-if="themeConfig.showTag"/>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -56,7 +55,6 @@ const activeMenu = computed(() => {
return path return path
}) })
// //
const themeConfig = computed(() =>SettingStore.themeConfig) const themeConfig = computed(() =>SettingStore.themeConfig)
const isCollapse = computed(() =>!SettingStore.isCollapse) const isCollapse = computed(() =>!SettingStore.isCollapse)
@ -64,6 +62,5 @@ const isCollapse = computed(() =>!SettingStore.isCollapse)
<style lang="scss" scoped> <style lang="scss" scoped>
@import "./index.scss"; @import "./index.scss";
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<el-breadcrumb class="app-breadcrumb" separator="/"> <el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb" mode="out-in"> <transition-group name="breadcrumb" >
<el-breadcrumb-item :to="{ path: '/' }" key="home" v-if="matched[0].meta.title !== '首页'"> <el-breadcrumb-item :to="{ path: '/' }" key="home" v-if="matched[0].meta.title !== '首页'">
<div class="breadcrumb-item"> <div class="breadcrumb-item">
<span class="breadcrumb-title">首页</span> <span class="breadcrumb-title">首页</span>

View File

@ -25,9 +25,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import Logo from './components/Logo.vue' import Logo from './components/Logo.vue'
import SubItem from '../SubMenu/SubItem.vue' import SubItem from '../SubMenu/SubItem.vue'
import SubMenu from '../SubMenu/SubMenu.vue'
import {useSettingStore} from "@/store/modules/setting" import {useSettingStore} from "@/store/modules/setting"
import {usePermissionStore} from "@/store/modules/permission" import {usePermissionStore} from "@/store/modules/permission"
import { computed } from 'vue' import { computed, ref, watch } from "vue";
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
// setupstore // setupstore
@ -40,6 +41,8 @@ const isCollapse = computed(() => !SettingStore.isCollapse)
// //
const themeConfig = computed(() =>SettingStore.themeConfig ) const themeConfig = computed(() =>SettingStore.themeConfig )
const basePath = ref('/')
// //
const permission_routes = computed(() => PermissionStore.permission_routes) const permission_routes = computed(() => PermissionStore.permission_routes)

View File

@ -11,7 +11,7 @@
</el-menu-item> </el-menu-item>
</app-link> </app-link>
</template> </template>
<el-sub-menu :index="resolvePath(item.path)" v-else popper-append-to-body> <el-sub-menu :index="resolvePath(item.path)" v-else >
<template #title> <template #title>
<el-icon :size="20"> <component :is="item.meta?.icon"></component></el-icon> <el-icon :size="20"> <component :is="item.meta?.icon"></component></el-icon>
<span>{{ generateTitle(item) }}</span> <span>{{ generateTitle(item) }}</span>
@ -45,6 +45,7 @@ const props = defineProps({
const onlyOneChild = ref(null) const onlyOneChild = ref(null)
const hasOneShowingChild = (children = [], parent) => { const hasOneShowingChild = (children = [], parent) => {
const showingChildren = children.filter((item) => { const showingChildren = children.filter((item) => {
// //
if (item.hidden) { if (item.hidden) {
@ -55,7 +56,6 @@ const hasOneShowingChild = (children = [], parent) => {
return true return true
} }
}) })
// //
if (showingChildren.length === 1) { if (showingChildren.length === 1) {
return true return true

View File

@ -0,0 +1,43 @@
<template>
<template v-for="subItem in menuList" :key="subItem.path">
<el-sub-menu v-if="subItem.children && subItem.children.length > 0" :index="subItem.path">
<template #title>
<el-icon>
<component :is="subItem?.meta?.icon"></component>
</el-icon>
<span>{{ subItem?.meta?.title }}</span>
</template>
<SubMenu :menuList="subItem.children" :basePath="`${basePath?'/':''}${subItem.path}`"/>
</el-sub-menu>
<el-menu-item v-else-if="!subItem.hidden" :index="subItem.path" @click="handleClickMenu(subItem)">
<el-icon>
<component :is="subItem?.meta?.icon"></component>
</el-icon>
<template #title>
<span>{{ subItem?.meta?.title }}</span>
</template>
</el-menu-item>
</template>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
import { isExternal } from '@/utils/validate.js'
let props = defineProps({
menuList:{
type:Array,
default:()=>[]
},
basePath: {
type: String,
default: '',
},
})
const router = useRouter();
const handleClickMenu = (subItem) => {
if (isExternal(subItem.path)) return window.open(subItem.path, "_blank");
let path = props.basePath+'/'+subItem.path
router.push(path);
};
</script>

View File

@ -15,12 +15,14 @@
import { useResizeHandler } from './hooks/useResizeHandler' import { useResizeHandler } from './hooks/useResizeHandler'
import LayoutVertical from './LayoutVertical/index.vue' import LayoutVertical from './LayoutVertical/index.vue'
import LayoutHorizontal from './LayoutHorizontal/index.vue' import LayoutHorizontal from './LayoutHorizontal/index.vue'
import LayoutColumns from './LayoutColumns/index.vue'
const SettingStore = useSettingStore() const SettingStore = useSettingStore()
const themeConfig = computed(() => SettingStore.themeConfig) const themeConfig = computed(() => SettingStore.themeConfig)
const LayoutComponents = { const LayoutComponents = {
horizontal: LayoutHorizontal, horizontal: LayoutHorizontal,
vertical: LayoutVertical, vertical: LayoutVertical,
columns: LayoutColumns,
}; };
// //

View File

@ -73,6 +73,7 @@ export const constantRoutes: Array<RouteRecordRaw&extendRoute> = [
name: 'layout', name: 'layout',
component: Layout, component: Layout,
redirect: '/home', redirect: '/home',
meta: { title: '首页', icon: 'House', },
children: [ children: [
{ {
path: '/home', path: '/home',

View File

@ -5,7 +5,7 @@ import Layout from "@/layout/index.vue";
const chartsRouter = [{ const chartsRouter = [{
path: '/chat', path: '/chat',
component: Layout, component: Layout,
redirect: '/charts/index', redirect: '/chat/index',
name: 'chat', name: 'chat',
meta: { meta: {
title: '聊天框', title: '聊天框',

View File

@ -5,7 +5,7 @@ import Layout from "@/layout/index.vue";
const externalLink = [{ const externalLink = [{
path: '/external-link', path: '/external-link',
component: Layout, component: Layout,
redirect: 'noRedirect', redirect: '/external-link/wechat',
name: 'external-link', name: 'external-link',
meta: { meta: {
title: '外部链接', title: '外部链接',
@ -29,7 +29,7 @@ const externalLink = [{
meta: { title: 'Gitee 地址', icon: 'MenuIcon' } meta: { title: 'Gitee 地址', icon: 'MenuIcon' }
}, },
{ {
path: 'http://182.61.5.190:8890/#/login', path: 'https://yuanzbz.gitee.io/vue-admin-simple',
name: 'simple', name: 'simple',
meta: { title: '简易版本', icon: 'MenuIcon' }, meta: { title: '简易版本', icon: 'MenuIcon' },
component: () => import('@/views/externalLinks/simple/index.vue'), component: () => import('@/views/externalLinks/simple/index.vue'),

View File

@ -7,7 +7,7 @@ import Layout from "@/layout/index.vue";
const nestedRouter = [{ const nestedRouter = [{
path: '/nested', path: '/nested',
component: Layout, component: Layout,
redirect: '/form/menu1', redirect: '/nested/menu1',
name: 'nested', name: 'nested',
meta: { meta: {
title: '路由嵌套', title: '路由嵌套',
@ -20,6 +20,7 @@ const nestedRouter = [{
name: 'menu1', name: 'menu1',
meta: { title: '菜单1', icon: 'MenuIcon' }, meta: { title: '菜单1', icon: 'MenuIcon' },
alwaysShow:true, alwaysShow:true,
redirect: '/nested/menu1/menu1-1',
children: [ children: [
{ {
path: 'menu1-1', path: 'menu1-1',

View File

@ -5,7 +5,7 @@ import Layout from '@/layout/index.vue'
const systemRouter = [{ const systemRouter = [{
path: '/system', path: '/system',
component: Layout, component: Layout,
redirect: '/system/page', redirect: '/system/user',
name: 'system', name: 'system',
meta: { meta: {
title: '系统管理', title: '系统管理',

View File

@ -20,15 +20,15 @@ const tableRouter = [{
meta: { title: '综合表格', keepAlive: true , icon: 'MenuIcon'} meta: { title: '综合表格', keepAlive: true , icon: 'MenuIcon'}
}, },
{ {
path: 'inline-table', path: 'inlineTable',
component: () => import('@/views/table/InlineEditTable/index.vue'), component: () => import('@/views/table/InlineEditTable/index.vue'),
name: 'inline-table', name: 'inlineTable',
meta: { title: '行内编辑', keepAlive: true , icon: 'MenuIcon'} meta: { title: '行内编辑', keepAlive: true , icon: 'MenuIcon'}
}, },
{ {
path: 'editableProTable', path: 'editableProTable',
component: () => import('@/views/table/EditableProTable/index.vue'), component: () => import('@/views/table/EditableProTable/index.vue'),
name: 'edit-table', name: 'editableProTable',
meta: { title: '可编辑表格', keepAlive: true , icon: 'MenuIcon'} meta: { title: '可编辑表格', keepAlive: true , icon: 'MenuIcon'}
}, },
// { // {

View File

@ -18,7 +18,7 @@ export const useSettingStore = defineStore({
themeConfig:{ themeConfig:{
// 显示设置 // 显示设置
showSetting:false, showSetting:false,
// 菜单展示模式 默认 vertical horizontal / vertical // 菜单展示模式 默认 vertical horizontal / vertical /columns
mode: 'vertical', mode: 'vertical',
// tagsView 是否展示 默认展示 // tagsView 是否展示 默认展示
showTag:true, showTag:true,

View File

@ -43,9 +43,14 @@
<style lang="scss" scoped> <style lang="scss" scoped>
.wscn-http403-container { .wscn-http403-container {
transform: translate(-50%); width: 100%;
position: absolute; height: 100%;
left: 50%; display: flex;
align-items: center;
justify-content: center;
}
a{
text-decoration: none;
} }
.wscn-http403 { .wscn-http403 {
position: relative; position: relative;

View File

@ -11,7 +11,7 @@
</div> </div>
<div class="bullshit"> <div class="bullshit">
<div class="bullshit__oops">页面不存在</div> <div class="bullshit__oops">页面不存在</div>
<div class="bullshit__headline">{{ message }}</div> <div class="bullshit__headline">您没有操作角色...</div>
<div class="bullshit__info">请检查URL地址是否正确, 或点击回到首页</div> <div class="bullshit__info">请检查URL地址是否正确, 或点击回到首页</div>
<router-link to="/" class="bullshit__return-home">回到首页</router-link> <router-link to="/" class="bullshit__return-home">回到首页</router-link>
</div> </div>
@ -25,9 +25,14 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.wscn-http404-container{ .wscn-http404-container{
transform: translate(-50%); width: 100%;
position: absolute; height: 100%;
left: 50%; display: flex;
align-items: center;
justify-content: center;
}
a{
text-decoration: none;
} }
.wscn-http404 { .wscn-http404 {
position: relative; position: relative;