update
This commit is contained in:
parent
668cfa8095
commit
e570a7e509
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite App</title>
|
<title>Vue3 Element Admin</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export default [
|
|||||||
url: "/api/login",
|
url: "/api/login",
|
||||||
method: "post",
|
method: "post",
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
|
statusCode: 200,
|
||||||
response: {
|
response: {
|
||||||
code: 200,
|
code: 200,
|
||||||
message: "登录成功",
|
message: "登录成功",
|
||||||
|
|||||||
38
mock/menu.js
Normal file
38
mock/menu.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export default [
|
||||||
|
{
|
||||||
|
url: "/api/menus",
|
||||||
|
method: "get",
|
||||||
|
timeout: 100,
|
||||||
|
response: {
|
||||||
|
code: 200,
|
||||||
|
message: "获取菜单成功",
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
url: '/test',
|
||||||
|
title: '测试页面',
|
||||||
|
icon: 'el-icon-location',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
url: '/test',
|
||||||
|
title: '列表',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/test/auth',
|
||||||
|
title: '权限页面',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: '/test/nest',
|
||||||
|
title: '二级菜单',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
url: '/test/nest',
|
||||||
|
title: '子菜单',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
1731
package-lock.json
generated
1731
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -28,10 +28,11 @@
|
|||||||
"@vue/compiler-sfc": "^3.0.5",
|
"@vue/compiler-sfc": "^3.0.5",
|
||||||
"autoprefixer": "^10.2.5",
|
"autoprefixer": "^10.2.5",
|
||||||
"element-plus": "^1.0.2-beta.35",
|
"element-plus": "^1.0.2-beta.35",
|
||||||
"less": "^4.1.1",
|
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
|
"sass": "^1.32.8",
|
||||||
"vite": "^2.1.0",
|
"vite": "^2.1.0",
|
||||||
"vite-plugin-mock": "^2.3.0"
|
"vite-plugin-mock": "^2.3.0",
|
||||||
|
"vite-plugin-svg-icons": "^0.4.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
<style lang="less">
|
<style lang="scss">
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#app {
|
#app {
|
||||||
|
|||||||
10
src/api/menu.js
Normal file
10
src/api/menu.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
|
||||||
|
// 获取菜单
|
||||||
|
export const GetMenus = () => {
|
||||||
|
return request({
|
||||||
|
url: "/api/menus",
|
||||||
|
method: "get"
|
||||||
|
});
|
||||||
|
};
|
||||||
1
src/assets/error-icons/403.svg
Normal file
1
src/assets/error-icons/403.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 13 KiB |
1
src/assets/error-icons/404.svg
Normal file
1
src/assets/error-icons/404.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 7.9 KiB |
1
src/assets/error-icons/500.svg
Normal file
1
src/assets/error-icons/500.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
7
src/assets/style/element-variables.scss
Normal file
7
src/assets/style/element-variables.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/* 改变主题色变量 */
|
||||||
|
$--color-primary: $mainColor;
|
||||||
|
|
||||||
|
/* 改变 icon 字体路径变量,必需 */
|
||||||
|
$--font-path: "element-plus/lib/theme-chalk/fonts";
|
||||||
|
|
||||||
|
@import "element-plus/packages/theme-chalk/src/index";
|
||||||
17
src/assets/style/global-variables.scss
Normal file
17
src/assets/style/global-variables.scss
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 该文件中的变量是全局变量,在css文件和vue组件中可以直接使用
|
||||||
|
|
||||||
|
$mainColor: #409eff; // 网站主题色
|
||||||
|
|
||||||
|
// 菜单配置
|
||||||
|
$menuBg: #304156; // 菜单背景颜色
|
||||||
|
$menuTextColor: #fff; // 菜单文字颜色
|
||||||
|
$menuActiveTextColor: $mainColor; // 已选中菜单文字颜色
|
||||||
|
$menuActiveBg: none; // 已选中菜单背景颜色
|
||||||
|
$menuHover: #263445; // 鼠标经过菜单时的背景颜色
|
||||||
|
$subMenuBg: #1f2d3d; // 子菜单背景颜色
|
||||||
|
$subMenuHover: #001528; // 鼠标经过子菜单时的背景颜色
|
||||||
|
$collapseMenuActiveBg: #1f2d3d; // 菜单宽度折叠后,已选中菜单的背景颜色
|
||||||
|
$collapseMenuActiveColor: $menuTextColor; // 菜单宽度折叠后,已选中菜单的文字颜色
|
||||||
|
$collapseMenuActiveBorderBg: $mainColor; // 菜单宽度折叠后,已选中菜单的边框颜色
|
||||||
|
$collapseMenuActiveBorderWidth: 2px; // 菜单宽度折叠后,已选中菜单的边框宽度
|
||||||
|
$arrowColor: #909399; // 展开/收起箭头颜色
|
||||||
1
src/assets/svg/home.svg
Normal file
1
src/assets/svg/home.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M510.72 41.856l435.2 361.024a32 32 0 0 1-36.16 52.48l-4.736-3.2-41.024-34.048V832c0 48.832-32.64 90.752-76.864 95.552l-8.448 0.448H245.312c-45.696 0-80.96-39.04-84.928-86.912L160 832V407.808l-55.04 44.608a32 32 0 0 1-40.96-0.64l-4.032-4.16a32 32 0 0 1 0.64-40.96l4.096-4.032L510.784 41.856z m-0.512 82.688L223.232 356.672a31.808 31.808 0 0 1 0.256 1.152l0.512 5.76V832c0 16.768 8.64 28.992 17.92 31.552l3.392 0.448h533.376c9.216 0 18.88-10.432 20.928-25.92L800 832V364.992L510.208 124.544z" /></svg>
|
||||||
|
After Width: | Height: | Size: 766 B |
@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">Vite Documentation</a> |
|
|
||||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<button @click="state.count++">count is: {{ state.count }}</button>
|
|
||||||
<p>
|
|
||||||
Edit
|
|
||||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, reactive } from 'vue'
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
msg: String
|
|
||||||
})
|
|
||||||
|
|
||||||
const state = reactive({ count: 0 })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
40
src/components/SvgIcon/index.vue
Normal file
40
src/components/SvgIcon/index.vue
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
class="icon"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use :xlink:href="symbolId" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent, computed } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "SvgIcon",
|
||||||
|
props: {
|
||||||
|
prefix: {
|
||||||
|
type: String,
|
||||||
|
default: "icon",
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
|
||||||
|
return { symbolId };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.icon {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
fill: currentColor;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1
src/globalComponents.js
Normal file
1
src/globalComponents.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default as SvgIcon } from "@/components/SvgIcon/index.vue";
|
||||||
37
src/layout/components/Sidebar/Item.vue
Normal file
37
src/layout/components/Sidebar/Item.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<i
|
||||||
|
v-if="isElementIcon"
|
||||||
|
:class="`icon ${icon}`"
|
||||||
|
/>
|
||||||
|
<svg-icon
|
||||||
|
class="icon"
|
||||||
|
v-else-if="!!icon"
|
||||||
|
:name="icon"
|
||||||
|
/>
|
||||||
|
<span>{{title}}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { computed, defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: ["title", "icon"],
|
||||||
|
setup({ icon }) {
|
||||||
|
const isElementIcon = computed(() => icon && icon.startsWith("el-icon"));
|
||||||
|
|
||||||
|
return {
|
||||||
|
isElementIcon,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -5,7 +5,7 @@
|
|||||||
src="~@/assets/logo.png"
|
src="~@/assets/logo.png"
|
||||||
@click="goHome"
|
@click="goHome"
|
||||||
>
|
>
|
||||||
<div class="title">ERP管理系统</div>
|
<div class="title">Vue3 Element Admin</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
@ -22,7 +22,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.brand {
|
.brand {
|
||||||
height: 48px;
|
height: 48px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
@ -36,7 +36,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
|||||||
@ -6,12 +6,16 @@
|
|||||||
:uniqueOpened="true"
|
:uniqueOpened="true"
|
||||||
:router="true"
|
:router="true"
|
||||||
:default-active="activePath"
|
:default-active="activePath"
|
||||||
background-color="#2d3a4b"
|
:background-color="variables.menuBg"
|
||||||
text-color="#fff"
|
:text-color="variables.menuTextColor"
|
||||||
active-text-color="#fff"
|
:active-text-color="variables.menuActiveTextColor"
|
||||||
>
|
>
|
||||||
|
|
||||||
<submenu :menus="menus" />
|
<submenu
|
||||||
|
v-for="menu in menus"
|
||||||
|
:key="menu.url"
|
||||||
|
:menu="menu"
|
||||||
|
/>
|
||||||
|
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
@ -21,6 +25,7 @@ import { computed, defineComponent } from "vue";
|
|||||||
import Submenu from "./Submenu.vue";
|
import Submenu from "./Submenu.vue";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
import config from "./config/menu.module.scss";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -33,19 +38,70 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const menus = computed(() => store.state.menu.menus);
|
const store = useStore();
|
||||||
const activePath = computed(() => route.path);
|
store.dispatch(
|
||||||
|
"menu/generateMenus",
|
||||||
|
store.state.account.userinfo && store.state.account.userinfo.role
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
menus,
|
menus: computed(() => store.state.menu.menus),
|
||||||
activePath,
|
activePath: computed(() => route.path),
|
||||||
|
variables: computed(() => config),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="scss">
|
||||||
|
// menu hover
|
||||||
|
.el-menu-item,
|
||||||
|
.el-submenu__title {
|
||||||
|
&:hover {
|
||||||
|
background-color: $menuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-submenu {
|
||||||
|
.el-menu-item,
|
||||||
|
.el-submenu .el-submenu__title {
|
||||||
|
background-color: $subMenuBg !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $subMenuHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-menu-item.is-active {
|
||||||
|
background-color: $menuActiveBg !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: $menuActiveBg !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu--collapse {
|
||||||
|
.el-menu-item.is-active,
|
||||||
|
.el-submenu.is-active > .el-submenu__title {
|
||||||
|
position: relative;
|
||||||
|
background-color: $collapseMenuActiveBg !important;
|
||||||
|
color: $collapseMenuActiveColor !important;
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: $collapseMenuActiveBorderWidth;
|
||||||
|
height: 100%;
|
||||||
|
background-color: $collapseMenuActiveBorderBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-submenu__title i {
|
||||||
|
color: $arrowColor;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="scss" scoped>
|
||||||
.scroll {
|
.scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@ -54,7 +110,4 @@ export default defineComponent({
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
::v-deep(.el-menu-item.is-active) {
|
|
||||||
background: #0174df !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@ -1,30 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-submenu
|
<el-menu-item
|
||||||
|
v-if="!menu.children"
|
||||||
|
:index="menu.url"
|
||||||
|
>
|
||||||
|
<item
|
||||||
|
:icon="menu.icon"
|
||||||
|
:title="menu.title"
|
||||||
|
/>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-submenu
|
||||||
|
v-else
|
||||||
:index="menu.url"
|
:index="menu.url"
|
||||||
v-for="(menu, index) in menus"
|
|
||||||
:key="index"
|
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<i class="el-icon-location"></i>
|
<item
|
||||||
<span>{{menu.title}}</span>
|
:icon="menu.icon"
|
||||||
|
:title="menu.title"
|
||||||
|
/>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<el-menu-item
|
<submenu
|
||||||
:index="submenu.url"
|
v-for="submenu in menu.children"
|
||||||
v-for="(submenu, subindex) in menu.children"
|
:key="submenu.url"
|
||||||
:key="subindex"
|
:is-nest="true"
|
||||||
>{{submenu.title}}</el-menu-item>
|
:menu="submenu"
|
||||||
|
/>
|
||||||
</el-submenu>
|
</el-submenu>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
import Item from "./Item.vue";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
name: "Submenu",
|
||||||
|
components: {
|
||||||
|
Item,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
menus: {
|
menu: {
|
||||||
type: Array,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
isNest: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup() {},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
5
src/layout/components/Sidebar/config/menu.module.scss
Normal file
5
src/layout/components/Sidebar/config/menu.module.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
:export {
|
||||||
|
menuBg: $menuBg;
|
||||||
|
menuTextColor: $menuTextColor;
|
||||||
|
menuActiveTextColor: $menuActiveTextColor;
|
||||||
|
}
|
||||||
@ -30,10 +30,10 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.left {
|
.left {
|
||||||
width: 210px;
|
width: 210px;
|
||||||
background: #2d3a4b;
|
background: $menuBg;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tabs"></div>
|
<div class="tabs"></div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.tabs {
|
.tabs {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-bottom: 1px solid #eaeaea;
|
border-bottom: 1px solid #eaeaea;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
>
|
>
|
||||||
<el-breadcrumb-item
|
<el-breadcrumb-item
|
||||||
v-for="(item, index) in breadcrumbs"
|
v-for="(item, index) in breadcrumbs"
|
||||||
:key="item.path"
|
:key="index"
|
||||||
:class="{no_link: index === breadcrumbs.length - 1}"
|
:class="{no_link: index === breadcrumbs.length - 1}"
|
||||||
:to="index < breadcrumbs.length - 1 ? item.path : ''"
|
:to="index < breadcrumbs.length - 1 ? item.path : ''"
|
||||||
>
|
>
|
||||||
@ -56,7 +56,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.breadcrumb {
|
.breadcrumb {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
::v-deep(a),
|
::v-deep(a),
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.fold-btn {
|
.fold-btn {
|
||||||
line-height: 48px;
|
line-height: 48px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|||||||
@ -29,9 +29,10 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userinfo = computed(() => store.state.login.userinfo);
|
const userinfo = computed(() => store.state.account.userinfo);
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
store.commit("app/clearToken");
|
store.commit("app/clearToken");
|
||||||
|
store.commit("account/clearUserinfo");
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
@ -42,7 +43,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.userinfo {
|
.userinfo {
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
line-height: 48px;
|
line-height: 48px;
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export default defineComponent({
|
|||||||
setup() {},
|
setup() {},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.header {
|
.header {
|
||||||
height: 48px;
|
height: 48px;
|
||||||
border-bottom: 1px solid #eaeaea;
|
border-bottom: 1px solid #eaeaea;
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.wrapper {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
26
src/main.js
26
src/main.js
@ -1,14 +1,28 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
// 引入路由
|
|
||||||
import router from './router'
|
const app = createApp(App)
|
||||||
// 引入store
|
|
||||||
import store from './store'
|
|
||||||
// 引入element-plus
|
// 引入element-plus
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
import "element-plus/lib/theme-chalk/index.css";
|
import './assets/style/element-variables.scss'
|
||||||
|
|
||||||
|
// 引入路由
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
// 引入store
|
||||||
|
import store from './store'
|
||||||
|
|
||||||
// 权限控制
|
// 权限控制
|
||||||
import './permission'
|
import './permission'
|
||||||
|
|
||||||
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')
|
// 引入svg图标注册脚本
|
||||||
|
import 'vite-plugin-svg-icons/register';
|
||||||
|
|
||||||
|
// 注册全局组件
|
||||||
|
import * as Components from './globalComponents'
|
||||||
|
Object.entries(Components).forEach(([key, component]) => {
|
||||||
|
app.component(key, component)
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(ElementPlus).use(store).use(router).mount('#app')
|
||||||
|
|||||||
@ -1,13 +1,27 @@
|
|||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import { TOKEN } from '@/store/modules/app' // TOKEN变量名
|
import { TOKEN } from '@/store/modules/app' // TOKEN变量名
|
||||||
// 白名单,里面是路由对象的name
|
|
||||||
const WhiteList = ['login']
|
|
||||||
|
|
||||||
// vue-router4的路由守卫不再是通过next放行,而是通过return返回false或者一个路由地址
|
const getPageTitle = title => {
|
||||||
|
const appTitle = store.state.app.title;
|
||||||
|
if (title) {
|
||||||
|
return `${title} - ${appTitle}`
|
||||||
|
}
|
||||||
|
return appTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
// 白名单,里面是路由对象的name
|
||||||
|
const WhiteList = ['login', 'forbidden', 'server-error', 'not-found']
|
||||||
|
|
||||||
|
// vue-router4的路由守卫不再是通过next放行,而是通过return返回true或false或者一个路由地址
|
||||||
router.beforeEach(async (to) => {
|
router.beforeEach(async (to) => {
|
||||||
|
|
||||||
|
document.title = getPageTitle(!!to.meta && to.meta.title)
|
||||||
|
|
||||||
|
if (WhiteList.includes(to.name)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (!window.localStorage[TOKEN]) {
|
if (!window.localStorage[TOKEN]) {
|
||||||
if (!WhiteList.includes(to.name)) {
|
|
||||||
return {
|
return {
|
||||||
name: 'login',
|
name: 'login',
|
||||||
query: {
|
query: {
|
||||||
@ -15,18 +29,21 @@ router.beforeEach(async (to) => {
|
|||||||
},
|
},
|
||||||
replace: true
|
replace: true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (!store.state.login.userinfo) {
|
let userinfo = store.state.account.userinfo
|
||||||
// 获取用户信息,根据用户角色生成菜单和动态路由
|
if (!userinfo) {
|
||||||
const userinfo = await store.dispatch("login/getUserinfo");
|
try {
|
||||||
const routes = await store.dispatch("menu/generateMenus", userinfo && userinfo.role)
|
// 获取用户信息
|
||||||
routes.forEach((item) => {
|
userinfo = await store.dispatch("account/getUserinfo");
|
||||||
router.addRoute(item);
|
} catch (err) {
|
||||||
});
|
return false
|
||||||
return to.fullPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// 如果没有权限,跳转到403页面
|
||||||
|
if (!!to.meta && !!to.meta.roles && !to.meta.roles.includes(userinfo.role)) {
|
||||||
|
return { path: '/403', replace: true }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1,13 +1,16 @@
|
|||||||
// index.js
|
// index.js
|
||||||
import { createRouter, createWebHashHistory } from "vue-router"
|
import { createRouter, createWebHashHistory } from "vue-router"
|
||||||
|
|
||||||
|
import error from './modules/error'
|
||||||
import login from './modules/login'
|
import login from './modules/login'
|
||||||
import home from './modules/home'
|
import home from './modules/home'
|
||||||
import user from './modules/user'
|
import test from './modules/test'
|
||||||
|
|
||||||
|
|
||||||
export const AllMenus = [
|
// 左侧菜单(左侧菜单请配置在此,否则无法显示)
|
||||||
...user
|
export const allMenus = [
|
||||||
|
...home,
|
||||||
|
...test,
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@ -18,7 +21,8 @@ const router = createRouter({
|
|||||||
redirect: '/home',
|
redirect: '/home',
|
||||||
},
|
},
|
||||||
...login,
|
...login,
|
||||||
...home,
|
...allMenus,
|
||||||
|
...error
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
60
src/router/modules/error.js
Normal file
60
src/router/modules/error.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import layout from '@/layout/index.vue'
|
||||||
|
const Error = () => import("@/views/error/index.vue");
|
||||||
|
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/error',
|
||||||
|
component: layout,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '403',
|
||||||
|
name: 'error-forbidden',
|
||||||
|
component: Error,
|
||||||
|
props: {
|
||||||
|
error: '403'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '500',
|
||||||
|
name: 'error-server-error',
|
||||||
|
component: Error,
|
||||||
|
props: {
|
||||||
|
error: '500'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '404',
|
||||||
|
name: 'error-not-found',
|
||||||
|
component: Error,
|
||||||
|
props: {
|
||||||
|
error: '404'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/403',
|
||||||
|
name: 'forbidden',
|
||||||
|
component: Error,
|
||||||
|
props: {
|
||||||
|
error: '403'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/500',
|
||||||
|
name: 'server-error',
|
||||||
|
component: Error,
|
||||||
|
props: {
|
||||||
|
error: '500'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:pathMatch(.*)*',
|
||||||
|
name: 'not-found',
|
||||||
|
component: Error,
|
||||||
|
props: {
|
||||||
|
error: '404'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
@ -6,6 +6,11 @@ export default [
|
|||||||
{
|
{
|
||||||
path: '/home',
|
path: '/home',
|
||||||
component: layout,
|
component: layout,
|
||||||
|
name: "Dashboard",
|
||||||
|
meta: {
|
||||||
|
title: "Dashboard",
|
||||||
|
},
|
||||||
|
icon: 'home',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
|
|||||||
80
src/router/modules/test.js
Normal file
80
src/router/modules/test.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import layout from '@/layout/index.vue'
|
||||||
|
const List = () => import("@/views/test/index.vue");
|
||||||
|
const Add = () => import("@/views/test/Add.vue");
|
||||||
|
const Auth = () => import("@/views/test/Auth.vue");
|
||||||
|
const Nest = () => import("@/views/test/Nest.vue");
|
||||||
|
const NestPage1 = () => import("@/views/test/nest/Page1.vue");
|
||||||
|
const NestPage2 = () => import("@/views/test/nest/Page2.vue");
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/test',
|
||||||
|
component: layout,
|
||||||
|
name: "test",
|
||||||
|
meta: {
|
||||||
|
title: "测试页面",
|
||||||
|
},
|
||||||
|
icon: 'el-icon-location',
|
||||||
|
roles: ["admin", "visitor"],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "testList",
|
||||||
|
component: List,
|
||||||
|
meta: {
|
||||||
|
title: "列表",
|
||||||
|
roles: ["admin", "visitor"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "add",
|
||||||
|
name: "testAdd",
|
||||||
|
component: Add,
|
||||||
|
meta: {
|
||||||
|
title: "添加",
|
||||||
|
roles: ["admin", "visitor"],
|
||||||
|
},
|
||||||
|
hidden: true, // 不在菜单中显示
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "auth",
|
||||||
|
name: "testAuth",
|
||||||
|
component: Auth,
|
||||||
|
meta: {
|
||||||
|
title: "权限页面",
|
||||||
|
roles: ["admin"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "nest",
|
||||||
|
name: "nest",
|
||||||
|
component: Nest,
|
||||||
|
redirect: '/test/nest/page1',
|
||||||
|
meta: {
|
||||||
|
title: "二级菜单",
|
||||||
|
roles: ["admin", "visitor"],
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "page1",
|
||||||
|
name: "nestPage1",
|
||||||
|
component: NestPage1,
|
||||||
|
meta: {
|
||||||
|
title: "page1",
|
||||||
|
roles: ["admin", "visitor"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "page2",
|
||||||
|
name: "nestPage2",
|
||||||
|
component: NestPage2,
|
||||||
|
meta: {
|
||||||
|
title: "page2",
|
||||||
|
roles: ["admin", "visitor"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import layout from '@/layout/index.vue'
|
|
||||||
const UserList = () => import("@/views/user/index.vue");
|
|
||||||
const AddUser = () => import("@/views/user/AddUser.vue");
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
path: '/user',
|
|
||||||
component: layout,
|
|
||||||
name: "user",
|
|
||||||
meta: {
|
|
||||||
title: "用户管理",
|
|
||||||
},
|
|
||||||
roles: ["admin", "visitor"],
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
name: "userList",
|
|
||||||
component: UserList,
|
|
||||||
meta: {
|
|
||||||
title: "用户列表",
|
|
||||||
},
|
|
||||||
roles: ["admin", "visitor"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "add",
|
|
||||||
name: "addUser",
|
|
||||||
component: AddUser,
|
|
||||||
meta: {
|
|
||||||
title: "添加用户"
|
|
||||||
},
|
|
||||||
hidden: true,
|
|
||||||
roles: ["admin"],
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,13 +1,13 @@
|
|||||||
//index.js
|
//index.js
|
||||||
import { createStore } from "vuex";
|
import { createStore } from "vuex";
|
||||||
import app from "./modules/app";
|
import app from "./modules/app";
|
||||||
import login from "./modules/login";
|
import account from "./modules/account";
|
||||||
import menu from "./modules/menu";
|
import menu from "./modules/menu";
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
modules: {
|
modules: {
|
||||||
app,
|
app,
|
||||||
login,
|
account,
|
||||||
menu
|
menu
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -8,6 +8,9 @@ export default {
|
|||||||
mutations: {
|
mutations: {
|
||||||
setUserinfo (state, data) {
|
setUserinfo (state, data) {
|
||||||
state.userinfo = data;
|
state.userinfo = data;
|
||||||
|
},
|
||||||
|
clearUserinfo (state) {
|
||||||
|
state.userinfo = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -4,6 +4,7 @@ export const TOKEN = "TOKEN";
|
|||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
|
title: 'Vue3 Element Admin',
|
||||||
authorization: getItem(TOKEN),
|
authorization: getItem(TOKEN),
|
||||||
sidebar: {
|
sidebar: {
|
||||||
collapse: getItem('collapse')
|
collapse: getItem('collapse')
|
||||||
|
|||||||
@ -1,35 +1,39 @@
|
|||||||
import { AllMenus } from '@/router'
|
import { allMenus } from '@/router'
|
||||||
|
import { GetMenus } from '@/api/menu'
|
||||||
|
|
||||||
const hasPermission = (role, route) => {
|
const hasPermission = (role, route) => {
|
||||||
if (!!route.roles && !route.roles.includes(role)) {
|
if (!!route.meta && !!route.meta.roles && !route.meta.roles.includes(role)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRoleMenus = (arr, role, parentPath = '') => {
|
const generateUrl = (path, parentPath) => {
|
||||||
|
return path.startsWith('/') ? path : (!!path ? `${parentPath}/${path}` : parentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFilterMenus = (arr, role, parentPath = '') => {
|
||||||
const menus = [];
|
const menus = [];
|
||||||
const routes = [];
|
|
||||||
|
|
||||||
arr.forEach(item => {
|
arr.forEach(item => {
|
||||||
if (hasPermission(role, item)) {
|
if (hasPermission(role, item) && !item.hidden) {
|
||||||
const menu = {
|
const menu = {
|
||||||
url: item.path.startsWith('/') ? item.path : (!!item.path ? `${parentPath}/${item.path}` : parentPath),
|
url: generateUrl(item.path, parentPath),
|
||||||
title: item.meta.title,
|
title: item.meta.title,
|
||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
}
|
}
|
||||||
const route = { ...item };
|
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
const { menus, routes } = getRoleMenus(item.children, role, menu.url)
|
if (item.children.length === 1) {
|
||||||
menu.children = menus
|
menu.url = generateUrl(item.children[0].path, menu.url)
|
||||||
route.children = routes
|
} else {
|
||||||
|
menu.children = getFilterMenus(item.children, role, menu.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
menus.push(menu)
|
menus.push(menu)
|
||||||
routes.push(route)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return { menus, routes }
|
return menus
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -43,14 +47,17 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
generateMenus ({ commit }, role) {
|
async generateMenus ({ commit }, role) {
|
||||||
if (!role || role === 'admin') {
|
// 方式一:根据角色生成菜单
|
||||||
commit('SET_MENUS', AllMenus)
|
const menus = getFilterMenus(allMenus, role)
|
||||||
} else {
|
|
||||||
const { menus, routes } = getRoleMenus(AllMenus, role)
|
|
||||||
commit('SET_MENUS', menus)
|
commit('SET_MENUS', menus)
|
||||||
return Promise.resolve(routes)
|
|
||||||
}
|
// // 方式二:从后台获取菜单
|
||||||
|
// const { code, data } = await GetMenus();
|
||||||
|
// if (+code === 200) {
|
||||||
|
// commit('SET_MENUS', data)
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -12,7 +12,7 @@ const service = axios.create({
|
|||||||
// 拦截请求
|
// 拦截请求
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const authorization = store.state.app;
|
const { authorization } = store.state.app;
|
||||||
if (authorization) {
|
if (authorization) {
|
||||||
config.headers.Authorization = `Bearer ${authorization.token}`;
|
config.headers.Authorization = `Bearer ${authorization.token}`;
|
||||||
}
|
}
|
||||||
@ -41,13 +41,14 @@ service.interceptors.response.use(
|
|||||||
router.push("/login");
|
router.push("/login");
|
||||||
|
|
||||||
// 代码不要往后执行了
|
// 代码不要往后执行了
|
||||||
return;
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
// 如果有refresh_token,则请求获取新的 token
|
// 如果有refresh_token,则请求获取新的 token
|
||||||
try {
|
try {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
url: "/api/authorizations",
|
url: "/api/authorizations",
|
||||||
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${authorization.refresh_token}`,
|
Authorization: `Bearer ${authorization.refresh_token}`,
|
||||||
},
|
},
|
||||||
@ -64,14 +65,16 @@ service.interceptors.response.use(
|
|||||||
return service(error.config);
|
return service(error.config);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 如果获取失败,直接跳转 登录页
|
// 如果获取失败,直接跳转 登录页
|
||||||
// console.log('请求刷线 token 失败', err)
|
// console.log('请求刷新 token 失败', err)
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
// 清除token
|
// 清除token
|
||||||
store.commit("app/clearToken")
|
store.commit("app/clearToken")
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ElMessage.error(error.response.message);
|
// console.dir(error) // 可在此进行错误上报
|
||||||
|
ElMessage.error(error.message);
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|||||||
77
src/views/error/index.vue
Normal file
77
src/views/error/index.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error">
|
||||||
|
<template v-if="error === '403'">
|
||||||
|
<span class="code-403">403</span>
|
||||||
|
<img
|
||||||
|
src="~@/assets/error-icons/403.svg"
|
||||||
|
alt=""
|
||||||
|
class="error-img"
|
||||||
|
>
|
||||||
|
<h2 class="title">您无权访问此页面</h2>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="error === '500'">
|
||||||
|
<img
|
||||||
|
src="~@/assets/error-icons/500.svg"
|
||||||
|
alt=""
|
||||||
|
class="error-img"
|
||||||
|
>
|
||||||
|
<h2 class="title">服务器出错了</h2>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="error === '404'">
|
||||||
|
<img
|
||||||
|
src="~@/assets/error-icons/404.svg"
|
||||||
|
alt=""
|
||||||
|
class="error-img"
|
||||||
|
>
|
||||||
|
<h2 class="title">您访问的页面不存在</h2>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<router-link to="/">
|
||||||
|
<el-button type="primary">返回首页</el-button>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: ["error"],
|
||||||
|
setup({ error }) {
|
||||||
|
const store = useStore();
|
||||||
|
const router = useRouter();
|
||||||
|
if (!!store.state.account.userinfo) {
|
||||||
|
router.replace(`/error/${error}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.error {
|
||||||
|
position: relative;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 48px;
|
||||||
|
.code-403 {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 50px;
|
||||||
|
top: 148px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(32px);
|
||||||
|
font-family: arial;
|
||||||
|
color: #ee5c42;
|
||||||
|
}
|
||||||
|
.error-img {
|
||||||
|
width: 320px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
margin: 32px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,3 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
home
|
<div class="home">home</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.home {
|
||||||
|
color: $mainColor;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -113,7 +113,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
.login {
|
.login {
|
||||||
transition: transform 1s;
|
transition: transform 1s;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
<template>
|
<template>
|
||||||
addUser
|
添加
|
||||||
</template>
|
</template>
|
||||||
3
src/views/test/Auth.vue
Normal file
3
src/views/test/Auth.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
权限页面
|
||||||
|
</template>
|
||||||
4
src/views/test/Nest.vue
Normal file
4
src/views/test/Nest.vue
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<template>
|
||||||
|
<h1>二级菜单</h1>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
3
src/views/test/index.vue
Normal file
3
src/views/test/index.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
列表
|
||||||
|
</template>
|
||||||
@ -1,3 +1,3 @@
|
|||||||
<template>
|
<template>
|
||||||
user
|
Page1
|
||||||
</template>
|
</template>
|
||||||
3
src/views/test/nest/Page2.vue
Normal file
3
src/views/test/nest/Page2.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
Page2
|
||||||
|
</template>
|
||||||
@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
|
|||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { viteMockServe } from "vite-plugin-mock";
|
import { viteMockServe } from "vite-plugin-mock";
|
||||||
|
import viteSvgIcons from 'vite-plugin-svg-icons';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default env => {
|
export default env => {
|
||||||
@ -15,7 +16,20 @@ export default env => {
|
|||||||
localEnabled: env.mode === "mock", // 指定在mock模式下才启动mock服务(可以在package.json的启动命令中指定mode为mock)
|
localEnabled: env.mode === "mock", // 指定在mock模式下才启动mock服务(可以在package.json的启动命令中指定mode为mock)
|
||||||
supportTs: false, // mockPath目录中的文件是否支持ts文件,现在我们不使用ts,所以设为false
|
supportTs: false, // mockPath目录中的文件是否支持ts文件,现在我们不使用ts,所以设为false
|
||||||
}),
|
}),
|
||||||
|
viteSvgIcons({
|
||||||
|
// 指定需要缓存的图标文件夹
|
||||||
|
iconDirs: [path.resolve(__dirname, 'src/assets/svg')],
|
||||||
|
// 指定symbolId格式
|
||||||
|
symbolId: 'icon-[dir]-[name]',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: '@import "./src/assets/style/global-variables.scss";' // 全局变量
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "src"),
|
"@": path.resolve(__dirname, "src"),
|
||||||
|
|||||||
18
项目开发手册.md
18
项目开发手册.md
@ -633,7 +633,7 @@ server: {
|
|||||||
// 拦截请求
|
// 拦截请求
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const authorization = store.state.app;
|
const { authorization } = store.state.app;
|
||||||
if (authorization) {
|
if (authorization) {
|
||||||
config.headers.Authorization = `Bearer ${authorization.token}`;
|
config.headers.Authorization = `Bearer ${authorization.token}`;
|
||||||
}
|
}
|
||||||
@ -662,13 +662,14 @@ server: {
|
|||||||
router.push("/login");
|
router.push("/login");
|
||||||
|
|
||||||
// 代码不要往后执行了
|
// 代码不要往后执行了
|
||||||
return;
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
// 如果有refresh_token,则请求获取新的 token
|
// 如果有refresh_token,则请求获取新的 token
|
||||||
try {
|
try {
|
||||||
const res = await axios({
|
const res = await axios({
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
url: "/api/authorizations",
|
url: "/api/authorizations",
|
||||||
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${authorization.refresh_token}`,
|
Authorization: `Bearer ${authorization.refresh_token}`,
|
||||||
},
|
},
|
||||||
@ -685,14 +686,15 @@ server: {
|
|||||||
return service(error.config);
|
return service(error.config);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// 如果获取失败,直接跳转 登录页
|
// 如果获取失败,直接跳转 登录页
|
||||||
// console.log('请求刷线 token 失败', err)
|
// console.log('请求刷新 token 失败', err)
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
// 清除token
|
// 清除token
|
||||||
store.commit("app/clearToken")
|
store.commit("app/clearToken")
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ElMessage.error(error.response.message);
|
ElMessage.error(error.message);
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
@ -760,16 +762,16 @@ server: {
|
|||||||
|
|
||||||
- css预处理器
|
- css预处理器
|
||||||
|
|
||||||
本项目我们使用less,先安装less
|
由于element-plus使用sass开发,所以本项目我们也使用sass,先安装sass
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
npm install -D less
|
npm install -D sass
|
||||||
```
|
```
|
||||||
|
|
||||||
使用less
|
使用sass
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<style lang="less" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user