This commit is contained in:
huzhushan 2021-04-01 17:54:18 +08:00
parent 668cfa8095
commit e570a7e509
51 changed files with 2270 additions and 294 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<title>Vue3 Element Admin</title>
</head>
<body>
<div id="app"></div>

View File

@ -3,6 +3,7 @@ export default [
url: "/api/login",
method: "post",
timeout: 1000,
statusCode: 200,
response: {
code: 200,
message: "登录成功",

38
mock/menu.js Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@ -28,10 +28,11 @@
"@vue/compiler-sfc": "^3.0.5",
"autoprefixer": "^10.2.5",
"element-plus": "^1.0.2-beta.35",
"less": "^4.1.1",
"mockjs": "^1.1.0",
"sass": "^1.32.8",
"vite": "^2.1.0",
"vite-plugin-mock": "^2.3.0"
"vite-plugin-mock": "^2.3.0",
"vite-plugin-svg-icons": "^0.4.0"
},
"repository": {
"type": "git",

View File

@ -1,7 +1,7 @@
<template>
<router-view />
</template>
<style lang="less">
<style lang="scss">
html,
body,
#app {

10
src/api/menu.js Normal file
View File

@ -0,0 +1,10 @@
import request from '@/utils/request'
// 获取菜单
export const GetMenus = () => {
return request({
url: "/api/menus",
method: "get"
});
};

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View 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";

View 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
View 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

View File

@ -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>

View 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
View File

@ -0,0 +1 @@
export { default as SvgIcon } from "@/components/SvgIcon/index.vue";

View 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>

View File

@ -5,7 +5,7 @@
src="~@/assets/logo.png"
@click="goHome"
>
<div class="title">ERP管理系统</div>
<div class="title">Vue3 Element Admin</div>
</div>
</template>
<script>
@ -22,7 +22,7 @@ export default defineComponent({
},
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.brand {
height: 48px;
padding: 0 8px;
@ -36,7 +36,7 @@ export default defineComponent({
}
.title {
color: #fff;
font-size: 16px;
font-size: 14px;
font-weight: 700;
white-space: nowrap;
margin-left: 8px;

View File

@ -6,12 +6,16 @@
:uniqueOpened="true"
:router="true"
:default-active="activePath"
background-color="#2d3a4b"
text-color="#fff"
active-text-color="#fff"
:background-color="variables.menuBg"
:text-color="variables.menuTextColor"
:active-text-color="variables.menuActiveTextColor"
>
<submenu :menus="menus" />
<submenu
v-for="menu in menus"
:key="menu.url"
:menu="menu"
/>
</el-menu>
</el-scrollbar>
@ -21,6 +25,7 @@ import { computed, defineComponent } from "vue";
import Submenu from "./Submenu.vue";
import { useStore } from "vuex";
import { useRoute } from "vue-router";
import config from "./config/menu.module.scss";
export default defineComponent({
components: {
@ -33,19 +38,70 @@ export default defineComponent({
},
},
setup() {
const store = useStore();
const route = useRoute();
const menus = computed(() => store.state.menu.menus);
const activePath = computed(() => route.path);
const store = useStore();
store.dispatch(
"menu/generateMenus",
store.state.account.userinfo && store.state.account.userinfo.role
);
return {
menus,
activePath,
menus: computed(() => store.state.menu.menus),
activePath: computed(() => route.path),
variables: computed(() => config),
};
},
});
</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 {
flex: 1;
overflow-x: hidden;
@ -54,7 +110,4 @@ export default defineComponent({
border: none;
}
}
::v-deep(.el-menu-item.is-active) {
background: #0174df !important;
}
</style>

View File

@ -1,30 +1,49 @@
<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"
v-for="(menu, index) in menus"
:key="index"
>
<template #title>
<i class="el-icon-location"></i>
<span>{{menu.title}}</span>
<item
:icon="menu.icon"
:title="menu.title"
/>
</template>
<el-menu-item
:index="submenu.url"
v-for="(submenu, subindex) in menu.children"
:key="subindex"
>{{submenu.title}}</el-menu-item>
<submenu
v-for="submenu in menu.children"
:key="submenu.url"
:is-nest="true"
:menu="submenu"
/>
</el-submenu>
</template>
<script>
import { defineComponent } from "vue";
import Item from "./Item.vue";
export default defineComponent({
name: "Submenu",
components: {
Item,
},
props: {
menus: {
type: Array,
menu: {
type: Object,
required: true,
},
isNest: {
type: Boolean,
default: false,
},
},
setup() {},
});
</script>
</script>

View File

@ -0,0 +1,5 @@
:export {
menuBg: $menuBg;
menuTextColor: $menuTextColor;
menuActiveTextColor: $menuActiveTextColor;
}

View File

@ -30,10 +30,10 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.left {
width: 210px;
background: #2d3a4b;
background: $menuBg;
transition: all 0.3s;
overflow: hidden;
display: flex;

View File

@ -1,7 +1,7 @@
<template>
<div class="tabs"></div>
</template>
<style lang="less" scoped>
<style lang="scss" scoped>
.tabs {
height: 32px;
border-bottom: 1px solid #eaeaea;

View File

@ -5,7 +5,7 @@
>
<el-breadcrumb-item
v-for="(item, index) in breadcrumbs"
:key="item.path"
:key="index"
:class="{no_link: index === breadcrumbs.length - 1}"
:to="index < breadcrumbs.length - 1 ? item.path : ''"
>
@ -56,7 +56,7 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.breadcrumb {
margin-left: 10px;
::v-deep(a),

View File

@ -22,7 +22,7 @@ export default defineComponent({
},
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.fold-btn {
line-height: 48px;
padding: 0 10px;

View File

@ -29,9 +29,10 @@ export default defineComponent({
setup() {
const store = useStore();
const router = useRouter();
const userinfo = computed(() => store.state.login.userinfo);
const userinfo = computed(() => store.state.account.userinfo);
const logout = () => {
store.commit("app/clearToken");
store.commit("account/clearUserinfo");
router.push("/login");
};
return {
@ -42,7 +43,7 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.userinfo {
padding: 0 16px;
line-height: 48px;

View File

@ -23,7 +23,7 @@ export default defineComponent({
setup() {},
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.header {
height: 48px;
border-bottom: 1px solid #eaeaea;

View File

@ -27,7 +27,7 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.wrapper {
display: flex;
height: 100%;

View File

@ -1,14 +1,28 @@
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import router from './router'
// 引入store
import store from './store'
const app = createApp(App)
// 引入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'
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')

View File

@ -1,32 +1,49 @@
import router from '@/router'
import store from '@/store'
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) => {
document.title = getPageTitle(!!to.meta && to.meta.title)
if (WhiteList.includes(to.name)) {
return true
}
if (!window.localStorage[TOKEN]) {
if (!WhiteList.includes(to.name)) {
return {
name: 'login',
query: {
redirect: to.path // redirect是指登录之后可以跳回到redirect指定的页面
},
replace: true
}
return {
name: 'login',
query: {
redirect: to.path // redirect是指登录之后可以跳回到redirect指定的页面
},
replace: true
}
} else {
if (!store.state.login.userinfo) {
// 获取用户信息,根据用户角色生成菜单和动态路由
const userinfo = await store.dispatch("login/getUserinfo");
const routes = await store.dispatch("menu/generateMenus", userinfo && userinfo.role)
routes.forEach((item) => {
router.addRoute(item);
});
return to.fullPath
}
let userinfo = store.state.account.userinfo
if (!userinfo) {
try {
// 获取用户信息
userinfo = await store.dispatch("account/getUserinfo");
} catch (err) {
return false
}
}
// 如果没有权限跳转到403页面
if (!!to.meta && !!to.meta.roles && !to.meta.roles.includes(userinfo.role)) {
return { path: '/403', replace: true }
}
}
})

View File

@ -1,13 +1,16 @@
// index.js
import { createRouter, createWebHashHistory } from "vue-router"
import error from './modules/error'
import login from './modules/login'
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({
@ -18,7 +21,8 @@ const router = createRouter({
redirect: '/home',
},
...login,
...home,
...allMenus,
...error
],
});

View 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'
}
},
]

View File

@ -6,6 +6,11 @@ export default [
{
path: '/home',
component: layout,
name: "Dashboard",
meta: {
title: "Dashboard",
},
icon: 'home',
children: [
{
path: "",

View 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"],
},
}
]
},
]
}
]

View File

@ -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"],
}
]
}
]

View File

@ -1,13 +1,13 @@
//index.js
import { createStore } from "vuex";
import app from "./modules/app";
import login from "./modules/login";
import account from "./modules/account";
import menu from "./modules/menu";
export default createStore({
modules: {
app,
login,
account,
menu
},
});

View File

@ -8,6 +8,9 @@ export default {
mutations: {
setUserinfo (state, data) {
state.userinfo = data;
},
clearUserinfo (state) {
state.userinfo = null;
}
},
actions: {

View File

@ -4,6 +4,7 @@ export const TOKEN = "TOKEN";
export default {
namespaced: true,
state: {
title: 'Vue3 Element Admin',
authorization: getItem(TOKEN),
sidebar: {
collapse: getItem('collapse')

View File

@ -1,35 +1,39 @@
import { AllMenus } from '@/router'
import { allMenus } from '@/router'
import { GetMenus } from '@/api/menu'
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 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 routes = [];
arr.forEach(item => {
if (hasPermission(role, item)) {
if (hasPermission(role, item) && !item.hidden) {
const menu = {
url: item.path.startsWith('/') ? item.path : (!!item.path ? `${parentPath}/${item.path}` : parentPath),
url: generateUrl(item.path, parentPath),
title: item.meta.title,
icon: item.icon,
}
const route = { ...item };
if (item.children) {
const { menus, routes } = getRoleMenus(item.children, role, menu.url)
menu.children = menus
route.children = routes
if (item.children.length === 1) {
menu.url = generateUrl(item.children[0].path, menu.url)
} else {
menu.children = getFilterMenus(item.children, role, menu.url)
}
}
menus.push(menu)
routes.push(route)
}
})
return { menus, routes }
return menus
}
export default {
@ -43,14 +47,17 @@ export default {
}
},
actions: {
generateMenus ({ commit }, role) {
if (!role || role === 'admin') {
commit('SET_MENUS', AllMenus)
} else {
const { menus, routes } = getRoleMenus(AllMenus, role)
commit('SET_MENUS', menus)
return Promise.resolve(routes)
}
async generateMenus ({ commit }, role) {
// 方式一:根据角色生成菜单
const menus = getFilterMenus(allMenus, role)
commit('SET_MENUS', menus)
// // 方式二:从后台获取菜单
// const { code, data } = await GetMenus();
// if (+code === 200) {
// commit('SET_MENUS', data)
// }
}
},
};

View File

@ -12,7 +12,7 @@ const service = axios.create({
// 拦截请求
service.interceptors.request.use(
(config) => {
const authorization = store.state.app;
const { authorization } = store.state.app;
if (authorization) {
config.headers.Authorization = `Bearer ${authorization.token}`;
}
@ -41,13 +41,14 @@ service.interceptors.response.use(
router.push("/login");
// 代码不要往后执行了
return;
return Promise.reject(error);
}
// 如果有refresh_token则请求获取新的 token
try {
const res = await axios({
method: "PUT",
url: "/api/authorizations",
timeout: 10000,
headers: {
Authorization: `Bearer ${authorization.refresh_token}`,
},
@ -64,14 +65,16 @@ service.interceptors.response.use(
return service(error.config);
} catch (err) {
// 如果获取失败,直接跳转 登录页
// console.log('请求刷线 token 失败', err)
// console.log('请求刷 token 失败', err)
router.push("/login");
// 清除token
store.commit("app/clearToken")
return Promise.reject(error);
}
}
ElMessage.error(error.response.message);
// console.dir(error) // 可在此进行错误上报
ElMessage.error(error.message);
return Promise.reject(error);
}

77
src/views/error/index.vue Normal file
View 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>

View File

@ -1,3 +1,8 @@
<template>
home
</template>
<div class="home">home</div>
</template>
<style lang="scss" scoped>
.home {
color: $mainColor;
}
</style>

View File

@ -113,7 +113,7 @@ export default defineComponent({
});
</script>
<style lang="less" scoped>
<style lang="scss" scoped>
.login {
transition: transform 1s;
transform: scale(1);

View File

@ -1,3 +1,3 @@
<template>
addUser
添加
</template>

3
src/views/test/Auth.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
权限页面
</template>

4
src/views/test/Nest.vue Normal file
View File

@ -0,0 +1,4 @@
<template>
<h1>二级菜单</h1>
<router-view />
</template>

3
src/views/test/index.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
列表
</template>

View File

@ -1,3 +1,3 @@
<template>
user
Page1
</template>

View File

@ -0,0 +1,3 @@
<template>
Page2
</template>

View File

@ -2,6 +2,7 @@ import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path";
import { viteMockServe } from "vite-plugin-mock";
import viteSvgIcons from 'vite-plugin-svg-icons';
// https://vitejs.dev/config/
export default env => {
@ -15,7 +16,20 @@ export default env => {
localEnabled: env.mode === "mock", // 指定在mock模式下才启动mock服务可以在package.json的启动命令中指定mode为mock
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: {
alias: {
"@": path.resolve(__dirname, "src"),

View File

@ -633,7 +633,7 @@ server: {
// 拦截请求
service.interceptors.request.use(
(config) => {
const authorization = store.state.app;
const { authorization } = store.state.app;
if (authorization) {
config.headers.Authorization = `Bearer ${authorization.token}`;
}
@ -662,13 +662,14 @@ server: {
router.push("/login");
// 代码不要往后执行了
return;
return Promise.reject(error);
}
// 如果有refresh_token则请求获取新的 token
try {
const res = await axios({
method: "PUT",
url: "/api/authorizations",
timeout: 10000,
headers: {
Authorization: `Bearer ${authorization.refresh_token}`,
},
@ -685,14 +686,15 @@ server: {
return service(error.config);
} catch (err) {
// 如果获取失败,直接跳转 登录页
// console.log('请求刷线 token 失败', err)
// console.log('请求刷 token 失败', err)
router.push("/login");
// 清除token
store.commit("app/clearToken")
return Promise.reject(error);
}
}
ElMessage.error(error.response.message);
ElMessage.error(error.message);
return Promise.reject(error);
}
@ -760,16 +762,16 @@ server: {
- css预处理器
本项目我们使用less先安装less
由于element-plus使用sass开发所以本项目我们也使用sass先安装sass
```powershell
npm install -D less
npm install -D sass
```
使用less
使用sass
```html
<style lang="less" scoped>
<style lang="scss" scoped>
</style>
```