update
This commit is contained in:
parent
be7002e3d6
commit
668cfa8095
@ -21,7 +21,8 @@ export default [
|
|||||||
message: "获取用户信息成功",
|
message: "获取用户信息成功",
|
||||||
data: {
|
data: {
|
||||||
id: 1,
|
id: 1,
|
||||||
userName: 'admin',
|
name: 'zhangsan',
|
||||||
|
role: 'visitor',
|
||||||
avatar: "@image('48x48', '#fb0a2a')"
|
avatar: "@image('48x48', '#fb0a2a')"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
21
package.json
21
package.json
@ -1,7 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "ec-admin-vue3",
|
"name": "erp-vue3",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "huzhushan",
|
||||||
|
"email": "huzhushan@126.com",
|
||||||
|
"url": "https://github.com/huzhushan"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "npm run mock",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"mock": "vite --mode mock",
|
"mock": "vite --mode mock",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
@ -26,5 +32,14 @@
|
|||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"vite": "^2.1.0",
|
"vite": "^2.1.0",
|
||||||
"vite-plugin-mock": "^2.3.0"
|
"vite-plugin-mock": "^2.3.0"
|
||||||
}
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/huzhushan/erp-vue3.git"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/huzhushan/erp-vue3/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/huzhushan/erp-vue3"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
// 获取用户信息
|
|
||||||
export const GetUserinfo = () => {
|
|
||||||
return request({
|
|
||||||
url: "/api/userinfo",
|
|
||||||
method: "get"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -7,4 +7,12 @@ export const Login = data => {
|
|||||||
method: "post",
|
method: "post",
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取登录用户信息
|
||||||
|
export const GetUserinfo = () => {
|
||||||
|
return request({
|
||||||
|
url: "/api/userinfo",
|
||||||
|
method: "get"
|
||||||
|
});
|
||||||
};
|
};
|
||||||
47
src/layout/components/Sidebar/Logo.vue
Normal file
47
src/layout/components/Sidebar/Logo.vue
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<div class="brand">
|
||||||
|
<img
|
||||||
|
class="logo"
|
||||||
|
src="~@/assets/logo.png"
|
||||||
|
@click="goHome"
|
||||||
|
>
|
||||||
|
<div class="title">ERP管理系统</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
const router = useRouter();
|
||||||
|
const goHome = () => {
|
||||||
|
router.push("/");
|
||||||
|
};
|
||||||
|
return { goHome };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.brand {
|
||||||
|
height: 48px;
|
||||||
|
padding: 0 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.logo {
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: 32px;
|
||||||
|
max-height: 32px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 8px;
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
60
src/layout/components/Sidebar/Menus.vue
Normal file
60
src/layout/components/Sidebar/Menus.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<el-scrollbar class="scroll">
|
||||||
|
<el-menu
|
||||||
|
class="menu"
|
||||||
|
:collapse="collapse"
|
||||||
|
:uniqueOpened="true"
|
||||||
|
:router="true"
|
||||||
|
:default-active="activePath"
|
||||||
|
background-color="#2d3a4b"
|
||||||
|
text-color="#fff"
|
||||||
|
active-text-color="#fff"
|
||||||
|
>
|
||||||
|
|
||||||
|
<submenu :menus="menus" />
|
||||||
|
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { computed, defineComponent } from "vue";
|
||||||
|
import Submenu from "./Submenu.vue";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
Submenu,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
collapse: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const menus = computed(() => store.state.menu.menus);
|
||||||
|
const activePath = computed(() => route.path);
|
||||||
|
|
||||||
|
return {
|
||||||
|
menus,
|
||||||
|
activePath,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.scroll {
|
||||||
|
flex: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
.menu {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::v-deep(.el-menu-item.is-active) {
|
||||||
|
background: #0174df !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
30
src/layout/components/Sidebar/Submenu.vue
Normal file
30
src/layout/components/Sidebar/Submenu.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<el-submenu
|
||||||
|
:index="menu.url"
|
||||||
|
v-for="(menu, index) in menus"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<i class="el-icon-location"></i>
|
||||||
|
<span>{{menu.title}}</span>
|
||||||
|
</template>
|
||||||
|
<el-menu-item
|
||||||
|
:index="submenu.url"
|
||||||
|
v-for="(submenu, subindex) in menu.children"
|
||||||
|
:key="subindex"
|
||||||
|
>{{submenu.title}}</el-menu-item>
|
||||||
|
</el-submenu>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
menus: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -3,77 +3,22 @@
|
|||||||
class="left"
|
class="left"
|
||||||
:class="{collapse:collapse}"
|
:class="{collapse:collapse}"
|
||||||
>
|
>
|
||||||
<div class="brand">
|
<logo />
|
||||||
<img
|
<menus :collapse="collapse" />
|
||||||
class="logo"
|
|
||||||
src="~@/assets/logo.png"
|
|
||||||
>
|
|
||||||
<div class="title">ERP管理系统</div>
|
|
||||||
</div>
|
|
||||||
<el-menu
|
|
||||||
class="menu"
|
|
||||||
:collapse="collapse"
|
|
||||||
:uniqueOpened="true"
|
|
||||||
default-active="2"
|
|
||||||
background-color="#2d3a4b"
|
|
||||||
text-color="#fff"
|
|
||||||
active-text-color="#fff"
|
|
||||||
>
|
|
||||||
<el-submenu index="1">
|
|
||||||
<template #title>
|
|
||||||
<i class="el-icon-location"></i>
|
|
||||||
<span>导航一</span>
|
|
||||||
</template>
|
|
||||||
<el-menu-item-group>
|
|
||||||
<template #title>分组一</template>
|
|
||||||
<el-menu-item index="1-1">选项1</el-menu-item>
|
|
||||||
<el-menu-item index="1-2">选项2</el-menu-item>
|
|
||||||
</el-menu-item-group>
|
|
||||||
<el-menu-item-group title="分组2">
|
|
||||||
<el-menu-item index="1-3">选项3</el-menu-item>
|
|
||||||
</el-menu-item-group>
|
|
||||||
<el-submenu index="1-4">
|
|
||||||
<template #title>选项4</template>
|
|
||||||
<el-menu-item index="1-4-1">选项1</el-menu-item>
|
|
||||||
</el-submenu>
|
|
||||||
</el-submenu>
|
|
||||||
<el-menu-item index="2">
|
|
||||||
<i class="el-icon-menu"></i>
|
|
||||||
<template #title>导航二</template>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item
|
|
||||||
index="3"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
<i class="el-icon-document"></i>
|
|
||||||
<template #title>导航三</template>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-menu-item index="4">
|
|
||||||
<i class="el-icon-setting"></i>
|
|
||||||
<template #title>导航四</template>
|
|
||||||
</el-menu-item>
|
|
||||||
<el-submenu index="5">
|
|
||||||
<template #title>
|
|
||||||
<i class="el-icon-location"></i>
|
|
||||||
<span>导航一</span>
|
|
||||||
</template>
|
|
||||||
<el-menu-item-group>
|
|
||||||
<template #title>分组一</template>
|
|
||||||
<el-menu-item index="5-1">选项1</el-menu-item>
|
|
||||||
<el-menu-item index="5-2">选项2</el-menu-item>
|
|
||||||
</el-menu-item-group>
|
|
||||||
<el-menu-item-group title="分组2">
|
|
||||||
<el-menu-item index="5-3">选项3</el-menu-item>
|
|
||||||
</el-menu-item-group>
|
|
||||||
</el-submenu>
|
|
||||||
</el-menu>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent, computed } from "vue";
|
import { defineComponent, computed } from "vue";
|
||||||
|
import Logo from "./Logo.vue";
|
||||||
|
import Menus from "./Menus.vue";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
Logo,
|
||||||
|
Menus,
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const collapse = computed(() => !!store.state.app.sidebar.collapse);
|
const collapse = computed(() => !!store.state.app.sidebar.collapse);
|
||||||
@ -91,36 +36,13 @@ export default defineComponent({
|
|||||||
background: #2d3a4b;
|
background: #2d3a4b;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
&.collapse {
|
&.collapse {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
.brand .title {
|
::v-deep(.brand .title) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.brand {
|
|
||||||
height: 48px;
|
|
||||||
padding: 0 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
.logo {
|
|
||||||
max-width: 32px;
|
|
||||||
max-height: 32px;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-left: 8px;
|
|
||||||
transition: all 0.5s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.menu {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
::v-deep(.el-menu-item.is-active) {
|
|
||||||
background: #0174df !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -14,34 +14,39 @@
|
|||||||
</el-breadcrumb>
|
</el-breadcrumb>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent, ref, onBeforeMount } from "vue";
|
import {
|
||||||
import { useRoute, useRouter, onBeforeRouteUpdate } from "vue-router";
|
defineComponent,
|
||||||
|
ref,
|
||||||
|
onBeforeMount,
|
||||||
|
getCurrentInstance,
|
||||||
|
watch,
|
||||||
|
} from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const { ctx } = getCurrentInstance();
|
||||||
const router = useRouter();
|
const route = ctx.$router.currentRoute; // 这里不使用useRoute获取当前路由,否则下面watch监听路由的时候会有警告
|
||||||
const routes = router.getRoutes();
|
|
||||||
const breadcrumbs = ref([]);
|
const breadcrumbs = ref([]);
|
||||||
|
|
||||||
const getBreadcrumbs = (route) => {
|
const getBreadcrumbs = (route) => {
|
||||||
const res = [{ path: "/", meta: { title: "首页" } }];
|
const home = [{ path: "/", meta: { title: "首页" } }];
|
||||||
const { parentBreadcrumb } = route.meta;
|
if (route.name === "home") {
|
||||||
if (!!parentBreadcrumb) {
|
return home;
|
||||||
const parents = routes.filter((item) =>
|
} else {
|
||||||
parentBreadcrumb.includes(item.name)
|
const matched = route.matched.filter(
|
||||||
|
(item) => !!item.meta && !!item.meta.title
|
||||||
);
|
);
|
||||||
res.push(...parents);
|
|
||||||
|
return [...home, ...matched];
|
||||||
}
|
}
|
||||||
if (route.name !== "home") res.push(route);
|
|
||||||
breadcrumbs.value = res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
getBreadcrumbs(route);
|
breadcrumbs.value = getBreadcrumbs(route.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeRouteUpdate((to) => {
|
watch(route, (newRoute) => {
|
||||||
getBreadcrumbs(to);
|
breadcrumbs.value = getBreadcrumbs(newRoute);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<img
|
<img
|
||||||
class="avatar"
|
class="avatar"
|
||||||
:src="userinfo.avatar"
|
:src="userinfo.avatar"
|
||||||
/> {{userinfo.userName}}
|
/> {{userinfo.name}}
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
@ -29,7 +29,7 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const userinfo = computed(() => store.state.app.userinfo);
|
const userinfo = computed(() => store.state.login.userinfo);
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
store.commit("app/clearToken");
|
store.commit("app/clearToken");
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
@ -53,7 +53,8 @@ export default defineComponent({
|
|||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
.el-icon-user {
|
.el-icon-user {
|
||||||
font-size: 16px;
|
font-size: 20px;
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
.avatar {
|
.avatar {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
|
|||||||
@ -13,30 +13,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent, onBeforeMount } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { useStore } from "vuex";
|
|
||||||
import Sidebar from "./components/Sidebar/index.vue";
|
import Sidebar from "./components/Sidebar/index.vue";
|
||||||
import Topbar from "./components/Topbar/index.vue";
|
import Topbar from "./components/Topbar/index.vue";
|
||||||
import Tabsbar from "./components/Tabsbar/index.vue";
|
import Tabsbar from "./components/Tabsbar/index.vue";
|
||||||
import { GetUserinfo } from "@/api/app";
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
Topbar,
|
Topbar,
|
||||||
Tabsbar,
|
Tabsbar,
|
||||||
},
|
},
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
const getUserinfo = async () => {
|
|
||||||
const { code, data } = await GetUserinfo();
|
|
||||||
if (+code === 200) {
|
|
||||||
store.commit("app/setUserinfo", data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
onBeforeMount(() => {
|
|
||||||
getUserinfo();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -8,4 +8,7 @@ import store from './store'
|
|||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
import "element-plus/lib/theme-chalk/index.css";
|
import "element-plus/lib/theme-chalk/index.css";
|
||||||
|
|
||||||
|
// 权限控制
|
||||||
|
import './permission'
|
||||||
|
|
||||||
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')
|
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')
|
||||||
|
|||||||
32
src/permission.js
Normal file
32
src/permission.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import router from '@/router'
|
||||||
|
import store from '@/store'
|
||||||
|
import { TOKEN } from '@/store/modules/app' // TOKEN变量名
|
||||||
|
// 白名单,里面是路由对象的name
|
||||||
|
const WhiteList = ['login']
|
||||||
|
|
||||||
|
// vue-router4的路由守卫不再是通过next放行,而是通过return返回false或者一个路由地址
|
||||||
|
router.beforeEach(async (to) => {
|
||||||
|
if (!window.localStorage[TOKEN]) {
|
||||||
|
if (!WhiteList.includes(to.name)) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -1,39 +1,25 @@
|
|||||||
// index.js
|
// index.js
|
||||||
import { createRouter, createWebHashHistory } from "vue-router"
|
import { createRouter, createWebHashHistory } from "vue-router"
|
||||||
import layout from '@/layout/index.vue'
|
|
||||||
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 user from './modules/user'
|
||||||
|
|
||||||
import { TOKEN } from '@/store/modules/app'
|
|
||||||
|
export const AllMenus = [
|
||||||
|
...user
|
||||||
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: layout,
|
|
||||||
redirect: '/home',
|
redirect: '/home',
|
||||||
children: [
|
|
||||||
...home,
|
|
||||||
...user
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
...login
|
...login,
|
||||||
|
...home,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// vue-router4的路由守卫不再是通过next放行,而是通过return返回false或者一个路由地址
|
|
||||||
router.beforeEach((to, from) => {
|
|
||||||
if (!window.localStorage[TOKEN] && to.name !== 'login') {
|
|
||||||
return {
|
|
||||||
name: 'login',
|
|
||||||
query: {
|
|
||||||
redirect: to.path // redirect是指登录之后可以跳回到redirect指定的页面
|
|
||||||
},
|
|
||||||
replace: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@ -1,13 +1,21 @@
|
|||||||
// home.js
|
// home.js
|
||||||
|
import layout from '@/layout/index.vue'
|
||||||
const Home = () => import("@/views/home/index.vue");
|
const Home = () => import("@/views/home/index.vue");
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
path: "/home",
|
path: '/home',
|
||||||
name: "home",
|
component: layout,
|
||||||
component: Home,
|
children: [
|
||||||
meta: {
|
{
|
||||||
title: "首页",
|
path: "",
|
||||||
}
|
name: "home",
|
||||||
}
|
component: Home,
|
||||||
|
meta: {
|
||||||
|
title: "首页",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
@ -1,23 +1,36 @@
|
|||||||
|
import layout from '@/layout/index.vue'
|
||||||
const User = () => import("@/views/user/index.vue");
|
const UserList = () => import("@/views/user/index.vue");
|
||||||
const AddUser = () => import("@/views/user/AddUser.vue");
|
const AddUser = () => import("@/views/user/AddUser.vue");
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
path: "/user",
|
path: '/user',
|
||||||
|
component: layout,
|
||||||
name: "user",
|
name: "user",
|
||||||
component: User,
|
|
||||||
meta: {
|
meta: {
|
||||||
title: "用户管理",
|
title: "用户管理",
|
||||||
}
|
},
|
||||||
},
|
roles: ["admin", "visitor"],
|
||||||
{
|
children: [
|
||||||
path: "/user/add",
|
{
|
||||||
name: "addUser",
|
path: "",
|
||||||
component: AddUser,
|
name: "userList",
|
||||||
meta: {
|
component: UserList,
|
||||||
title: "添加用户",
|
meta: {
|
||||||
parentBreadcrumb: ["user"]
|
title: "用户列表",
|
||||||
}
|
},
|
||||||
|
roles: ["admin", "visitor"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "add",
|
||||||
|
name: "addUser",
|
||||||
|
component: AddUser,
|
||||||
|
meta: {
|
||||||
|
title: "添加用户"
|
||||||
|
},
|
||||||
|
hidden: true,
|
||||||
|
roles: ["admin"],
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -1,9 +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 menu from "./modules/menu";
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
modules: {
|
modules: {
|
||||||
app
|
app,
|
||||||
|
login,
|
||||||
|
menu
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -7,8 +7,7 @@ export default {
|
|||||||
authorization: getItem(TOKEN),
|
authorization: getItem(TOKEN),
|
||||||
sidebar: {
|
sidebar: {
|
||||||
collapse: getItem('collapse')
|
collapse: getItem('collapse')
|
||||||
},
|
}
|
||||||
userinfo: null
|
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setToken (state, data) {
|
setToken (state, data) {
|
||||||
@ -30,12 +29,7 @@ export default {
|
|||||||
state.sidebar.collapse = '';
|
state.sidebar.collapse = '';
|
||||||
// 保存到localStorage
|
// 保存到localStorage
|
||||||
removeItem('collapse');
|
removeItem('collapse');
|
||||||
},
|
|
||||||
setUserinfo (state, data) {
|
|
||||||
state.userinfo = data;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {},
|
||||||
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
22
src/store/modules/login.js
Normal file
22
src/store/modules/login.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { GetUserinfo } from '@/api/login'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
userinfo: null
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setUserinfo (state, data) {
|
||||||
|
state.userinfo = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async getUserinfo ({ commit }) {
|
||||||
|
const { code, data } = await GetUserinfo();
|
||||||
|
if (+code === 200) {
|
||||||
|
commit("setUserinfo", data);
|
||||||
|
return Promise.resolve(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
56
src/store/modules/menu.js
Normal file
56
src/store/modules/menu.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { AllMenus } from '@/router'
|
||||||
|
|
||||||
|
const hasPermission = (role, route) => {
|
||||||
|
if (!!route.roles && !route.roles.includes(role)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRoleMenus = (arr, role, parentPath = '') => {
|
||||||
|
const menus = [];
|
||||||
|
const routes = [];
|
||||||
|
|
||||||
|
arr.forEach(item => {
|
||||||
|
if (hasPermission(role, item)) {
|
||||||
|
const menu = {
|
||||||
|
url: item.path.startsWith('/') ? item.path : (!!item.path ? `${parentPath}/${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
|
||||||
|
}
|
||||||
|
menus.push(menu)
|
||||||
|
routes.push(route)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return { menus, routes }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state: {
|
||||||
|
menus: []
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
SET_MENUS (state, data) {
|
||||||
|
state.menus = data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
147
项目开发手册.md
147
项目开发手册.md
@ -93,6 +93,42 @@ npm run dev
|
|||||||
|
|
||||||
> 目前项目中还没有路由、状态管理、UI组件库、ajax库
|
> 目前项目中还没有路由、状态管理、UI组件库、ajax库
|
||||||
|
|
||||||
|
### 组件库 Element Plus
|
||||||
|
|
||||||
|
- 安装element-plus,并且在main.js中引入
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install element-plus
|
||||||
|
```
|
||||||
|
|
||||||
|
引入element-plus
|
||||||
|
|
||||||
|
```js
|
||||||
|
// main.js
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
// 引入element-plus
|
||||||
|
import ElementPlus from "element-plus";
|
||||||
|
import "element-plus/lib/theme-chalk/index.css";
|
||||||
|
|
||||||
|
// 使用use注册ElementPlus
|
||||||
|
createApp(App).use(ElementPlus).mount('#app')
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- vue3中如何全局调用element-plus中的提示插件
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {getCurrentInstance} from 'vue';
|
||||||
|
|
||||||
|
setup () {
|
||||||
|
const { ctx } = getCurrentInstance(); // 可以把ctx当成vue2中的this
|
||||||
|
ctx.$message.success("yes")
|
||||||
|
ctx.$loading()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 路由
|
### 路由
|
||||||
|
|
||||||
- 在src目录中创建views目录,用来存放页面
|
- 在src目录中创建views目录,用来存放页面
|
||||||
@ -177,6 +213,16 @@ npm run dev
|
|||||||
export default router;
|
export default router;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- 修改App.vue
|
||||||
|
|
||||||
|
```html
|
||||||
|
|
||||||
|
```
|
||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
- 挂载路由
|
- 挂载路由
|
||||||
|
|
||||||
> 在main.js中挂载路由
|
> 在main.js中挂载路由
|
||||||
@ -185,15 +231,23 @@ npm run dev
|
|||||||
// main.js
|
// main.js
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
// 引入element-plus
|
||||||
|
import ElementPlus from "element-plus";
|
||||||
|
import "element-plus/lib/theme-chalk/index.css";
|
||||||
|
|
||||||
// 引入路由
|
// 引入路由
|
||||||
import router from './router'
|
import router from './router'
|
||||||
// 使用use注册路由
|
|
||||||
createApp(App).use(router).mount('#app')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
// 使用use注册路由
|
||||||
|
createApp(App).use(ElementPlus).use(router).mount('#app')
|
||||||
|
```
|
||||||
|
|
||||||
- 配置alias路径别名
|
- 配置alias路径别名
|
||||||
|
|
||||||
> 我们可以看到上面的引入路径比较深,这个时候可以配置一个src目录的别名,需要在vite.config.js中配置
|
> 文件引入路径比较深的时候,使用相对路径需要写很多`../`,例如上面的`router/modules/home.js`文件
|
||||||
|
>
|
||||||
|
> 所以我们可以配置一个src目录的别名,需要在vite.config.js中配置
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// vite.config.js
|
// vite.config.js
|
||||||
@ -209,7 +263,7 @@ npm run dev
|
|||||||
"@": path.resolve(__dirname, "src"),
|
"@": path.resolve(__dirname, "src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
然后我们就可以修改路由组件的引用路径了,在router/modules/home.js中
|
然后我们就可以修改路由组件的引用路径了,在router/modules/home.js中
|
||||||
@ -274,51 +328,19 @@ npm run dev
|
|||||||
// main.js
|
// main.js
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
// 引入路由
|
|
||||||
import router from './router'
|
|
||||||
// 引入store
|
|
||||||
import store from './store'
|
|
||||||
// 使用use注册路由和store
|
|
||||||
createApp(App).use(router).use(store).mount('#app')
|
|
||||||
```
|
|
||||||
|
|
||||||
### 组件库 Element Plus
|
|
||||||
|
|
||||||
- 安装element-plus,并且在main.js中引入
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
npm install element-plus
|
|
||||||
```
|
|
||||||
|
|
||||||
引入element-plus
|
|
||||||
|
|
||||||
```js
|
|
||||||
// main.js
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
import App from './App.vue'
|
|
||||||
// 引入路由
|
|
||||||
import router from './router'
|
|
||||||
// 引入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 "element-plus/lib/theme-chalk/index.css";
|
||||||
|
|
||||||
|
// 引入路由
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
|
// 引入store
|
||||||
|
import store from './store'
|
||||||
|
|
||||||
// 使用use注册路由和store
|
// 使用use注册路由和store
|
||||||
createApp(App).use(router).use(store).use(ElementPlus).mount('#app')
|
createApp(App).use(ElementPlus).use(router).use(store).mount('#app')
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
- vue3中如何全局调用element-plus中的提示插件
|
|
||||||
|
|
||||||
```js
|
|
||||||
import {getCurrentInstance} from 'vue';
|
|
||||||
|
|
||||||
setup () {
|
|
||||||
const { ctx } = getCurrentInstance(); // 可以把ctx当成vue2中的this
|
|
||||||
ctx.$message.success("yes")
|
|
||||||
ctx.$loading()
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 接口管理
|
### 接口管理
|
||||||
@ -440,6 +462,14 @@ server: {
|
|||||||
"@": path.resolve(__dirname, "src"),
|
"@": path.resolve(__dirname, "src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://dev.erp.com",
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@ -692,18 +722,22 @@ server: {
|
|||||||
### 权限控制
|
### 权限控制
|
||||||
|
|
||||||
> 如果没有登录或者token失效的时候,页面需要重定向到登录页
|
> 如果没有登录或者token失效的时候,页面需要重定向到登录页
|
||||||
|
>
|
||||||
|
> 还有一些例如找回密码的页面应该也可以访问,所以我们还是设置一个白名单,没有token的时候只能访问白名单的页面
|
||||||
|
|
||||||
- 全局路由守卫
|
- 在src目录创建permission.js
|
||||||
|
|
||||||
> 编辑router/index.js
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// 引入TOKEN变量
|
// permission.js
|
||||||
import { TOKEN } from '@/store/modules/app'
|
|
||||||
|
|
||||||
// 全局路由守卫,注意vue-router4的路由守卫不需要next跳转,而是通过return返回false或者一个路由地址
|
import router from '@/router'
|
||||||
router.beforeEach((to, from) => {
|
import { TOKEN } from '@/store/modules/app' // TOKEN变量名
|
||||||
if (!window.localStorage[TOKEN] && to.name !== 'login') {
|
// 白名单,里面是路由对象的name
|
||||||
|
const WhiteList = ['login']
|
||||||
|
|
||||||
|
// vue-router4的路由守卫不再是通过next放行,而是通过return返回false或者一个路由地址
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
if (!window.localStorage[TOKEN] && !WhiteList.includes(to.name)) {
|
||||||
return {
|
return {
|
||||||
name: 'login',
|
name: 'login',
|
||||||
query: {
|
query: {
|
||||||
@ -715,6 +749,13 @@ server: {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- 在main.js中引入permission
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 权限控制
|
||||||
|
import './permission'
|
||||||
|
```
|
||||||
|
|
||||||
### css
|
### css
|
||||||
|
|
||||||
- css预处理器
|
- css预处理器
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user