diff --git a/mock/login.js b/mock/login.js
index 1d95df0..58c9aba 100644
--- a/mock/login.js
+++ b/mock/login.js
@@ -21,7 +21,8 @@ export default [
message: "获取用户信息成功",
data: {
id: 1,
- userName: 'admin',
+ name: 'zhangsan',
+ role: 'visitor',
avatar: "@image('48x48', '#fb0a2a')"
}
},
diff --git a/package.json b/package.json
index 87812a4..df6e19c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,13 @@
{
- "name": "ec-admin-vue3",
- "version": "0.0.0",
+ "name": "erp-vue3",
+ "version": "1.0.0",
+ "author": {
+ "name": "huzhushan",
+ "email": "huzhushan@126.com",
+ "url": "https://github.com/huzhushan"
+ },
"scripts": {
+ "start": "npm run mock",
"dev": "vite",
"mock": "vite --mode mock",
"build": "vite build",
@@ -26,5 +32,14 @@
"mockjs": "^1.1.0",
"vite": "^2.1.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"
}
diff --git a/src/api/app.js b/src/api/app.js
deleted file mode 100644
index ddf38f6..0000000
--- a/src/api/app.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import request from '@/utils/request'
-// 获取用户信息
-export const GetUserinfo = () => {
- return request({
- url: "/api/userinfo",
- method: "get"
- });
-};
\ No newline at end of file
diff --git a/src/api/login.js b/src/api/login.js
index 27d559a..337c996 100644
--- a/src/api/login.js
+++ b/src/api/login.js
@@ -7,4 +7,12 @@ export const Login = data => {
method: "post",
data,
});
+};
+
+// 获取登录用户信息
+export const GetUserinfo = () => {
+ return request({
+ url: "/api/userinfo",
+ method: "get"
+ });
};
\ No newline at end of file
diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue
new file mode 100644
index 0000000..5bec2a1
--- /dev/null
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,47 @@
+
+
+

+
ERP管理系统
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/Menus.vue b/src/layout/components/Sidebar/Menus.vue
new file mode 100644
index 0000000..9157dd2
--- /dev/null
+++ b/src/layout/components/Sidebar/Menus.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/layout/components/Sidebar/Submenu.vue b/src/layout/components/Sidebar/Submenu.vue
new file mode 100644
index 0000000..46ccc70
--- /dev/null
+++ b/src/layout/components/Sidebar/Submenu.vue
@@ -0,0 +1,30 @@
+
+
+
+
+ {{menu.title}}
+
+ {{submenu.title}}
+
+
+
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
index 4f0f215..841b233 100644
--- a/src/layout/components/Sidebar/index.vue
+++ b/src/layout/components/Sidebar/index.vue
@@ -3,77 +3,22 @@
class="left"
:class="{collapse:collapse}"
>
-
-

-
ERP管理系统
-
-
+
+
diff --git a/src/main.js b/src/main.js
index 5bcce20..07eefd2 100644
--- a/src/main.js
+++ b/src/main.js
@@ -8,4 +8,7 @@ import store from './store'
import ElementPlus from "element-plus";
import "element-plus/lib/theme-chalk/index.css";
+// 权限控制
+import './permission'
+
createApp(App).use(store).use(router).use(ElementPlus).mount('#app')
diff --git a/src/permission.js b/src/permission.js
new file mode 100644
index 0000000..e82eef5
--- /dev/null
+++ b/src/permission.js
@@ -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
+ }
+
+
+ }
+})
\ No newline at end of file
diff --git a/src/router/index.js b/src/router/index.js
index 4982119..151521b 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,39 +1,25 @@
// index.js
import { createRouter, createWebHashHistory } from "vue-router"
-import layout from '@/layout/index.vue'
+
import login from './modules/login'
import home from './modules/home'
import user from './modules/user'
-import { TOKEN } from '@/store/modules/app'
+
+export const AllMenus = [
+ ...user
+]
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
- component: layout,
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;
\ No newline at end of file
diff --git a/src/router/modules/home.js b/src/router/modules/home.js
index 242912e..0ab35fd 100644
--- a/src/router/modules/home.js
+++ b/src/router/modules/home.js
@@ -1,13 +1,21 @@
// home.js
+import layout from '@/layout/index.vue'
const Home = () => import("@/views/home/index.vue");
export default [
{
- path: "/home",
- name: "home",
- component: Home,
- meta: {
- title: "首页",
- }
- }
+ path: '/home',
+ component: layout,
+ children: [
+ {
+ path: "",
+ name: "home",
+ component: Home,
+ meta: {
+ title: "首页",
+ }
+ }
+ ]
+ },
+
]
\ No newline at end of file
diff --git a/src/router/modules/user.js b/src/router/modules/user.js
index 9771daf..ff278de 100644
--- a/src/router/modules/user.js
+++ b/src/router/modules/user.js
@@ -1,23 +1,36 @@
-
-const User = () => import("@/views/user/index.vue");
+import layout from '@/layout/index.vue'
+const UserList = () => import("@/views/user/index.vue");
const AddUser = () => import("@/views/user/AddUser.vue");
export default [
{
- path: "/user",
+ path: '/user',
+ component: layout,
name: "user",
- component: User,
meta: {
title: "用户管理",
- }
- },
- {
- path: "/user/add",
- name: "addUser",
- component: AddUser,
- meta: {
- title: "添加用户",
- parentBreadcrumb: ["user"]
- }
+ },
+ 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"],
+ }
+ ]
}
]
\ No newline at end of file
diff --git a/src/store/index.js b/src/store/index.js
index 78a1fd0..26550f4 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,9 +1,13 @@
//index.js
import { createStore } from "vuex";
import app from "./modules/app";
+import login from "./modules/login";
+import menu from "./modules/menu";
export default createStore({
modules: {
- app
+ app,
+ login,
+ menu
},
});
\ No newline at end of file
diff --git a/src/store/modules/app.js b/src/store/modules/app.js
index 0fee57c..c08556f 100644
--- a/src/store/modules/app.js
+++ b/src/store/modules/app.js
@@ -7,8 +7,7 @@ export default {
authorization: getItem(TOKEN),
sidebar: {
collapse: getItem('collapse')
- },
- userinfo: null
+ }
},
mutations: {
setToken (state, data) {
@@ -30,12 +29,7 @@ export default {
state.sidebar.collapse = '';
// 保存到localStorage
removeItem('collapse');
- },
- setUserinfo (state, data) {
- state.userinfo = data;
}
},
- actions: {
-
- },
+ actions: {},
};
\ No newline at end of file
diff --git a/src/store/modules/login.js b/src/store/modules/login.js
new file mode 100644
index 0000000..2a57e7b
--- /dev/null
+++ b/src/store/modules/login.js
@@ -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)
+ }
+ }
+ },
+};
\ No newline at end of file
diff --git a/src/store/modules/menu.js b/src/store/modules/menu.js
new file mode 100644
index 0000000..a0e556c
--- /dev/null
+++ b/src/store/modules/menu.js
@@ -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)
+ }
+ }
+ },
+};
\ No newline at end of file
diff --git a/项目开发手册.md b/项目开发手册.md
index e4a51f7..6348509 100644
--- a/项目开发手册.md
+++ b/项目开发手册.md
@@ -93,6 +93,42 @@ npm run dev
> 目前项目中还没有路由、状态管理、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目录,用来存放页面
@@ -177,6 +213,16 @@ npm run dev
export default router;
```
+- 修改App.vue
+
+ ```html
+
+ ```
+
+
+
+ ```
+
- 挂载路由
> 在main.js中挂载路由
@@ -185,15 +231,23 @@ npm run dev
// 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";
+
// 引入路由
import router from './router'
- // 使用use注册路由
- createApp(App).use(router).mount('#app')
- ```
+ // 使用use注册路由
+ createApp(App).use(ElementPlus).use(router).mount('#app')
+ ```
+
- 配置alias路径别名
- > 我们可以看到上面的引入路径比较深,这个时候可以配置一个src目录的别名,需要在vite.config.js中配置
+ > 文件引入路径比较深的时候,使用相对路径需要写很多`../`,例如上面的`router/modules/home.js`文件
+>
+ > 所以我们可以配置一个src目录的别名,需要在vite.config.js中配置
```js
// vite.config.js
@@ -209,7 +263,7 @@ npm run dev
"@": path.resolve(__dirname, "src"),
},
},
- })
+})
```
然后我们就可以修改路由组件的引用路径了,在router/modules/home.js中
@@ -274,51 +328,19 @@ npm run dev
// main.js
import { createApp } from '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
import ElementPlus from "element-plus";
import "element-plus/lib/theme-chalk/index.css";
+ // 引入路由
+ import router from './router'
+
+ // 引入store
+ import store from './store'
+
// 使用use注册路由和store
- createApp(App).use(router).use(store).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()
- }
+ createApp(App).use(ElementPlus).use(router).use(store).mount('#app')
```
### 接口管理
@@ -440,6 +462,14 @@ server: {
"@": path.resolve(__dirname, "src"),
},
},
+ server: {
+ proxy: {
+ "/api": {
+ target: "http://dev.erp.com",
+ changeOrigin: true,
+ },
+ },
+ },
});
};
```
@@ -692,18 +722,22 @@ server: {
### 权限控制
> 如果没有登录或者token失效的时候,页面需要重定向到登录页
+>
+> 还有一些例如找回密码的页面应该也可以访问,所以我们还是设置一个白名单,没有token的时候只能访问白名单的页面
-- 全局路由守卫
-
- > 编辑router/index.js
+- 在src目录创建permission.js
```js
- // 引入TOKEN变量
- import { TOKEN } from '@/store/modules/app'
+// permission.js
- // 全局路由守卫,注意vue-router4的路由守卫不需要next跳转,而是通过return返回false或者一个路由地址
- router.beforeEach((to, from) => {
- if (!window.localStorage[TOKEN] && to.name !== 'login') {
+ import router from '@/router'
+ import { TOKEN } from '@/store/modules/app' // TOKEN变量名
+ // 白名单,里面是路由对象的name
+ const WhiteList = ['login']
+
+ // vue-router4的路由守卫不再是通过next放行,而是通过return返回false或者一个路由地址
+ router.beforeEach((to) => {
+ if (!window.localStorage[TOKEN] && !WhiteList.includes(to.name)) {
return {
name: 'login',
query: {
@@ -715,6 +749,13 @@ server: {
})
```
+- 在main.js中引入permission
+
+ ```js
+ // 权限控制
+ import './permission'
+ ```
+
### css
- css预处理器