From 668cfa809558ba48db3830a5b0e19220e57b1068 Mon Sep 17 00:00:00 2001 From: huzhushan Date: Fri, 26 Mar 2021 20:06:32 +0800 Subject: [PATCH] update --- mock/login.js | 3 +- package.json | 21 ++- src/api/app.js | 8 - src/api/login.js | 8 + src/layout/components/Sidebar/Logo.vue | 47 ++++++ src/layout/components/Sidebar/Menus.vue | 60 ++++++++ src/layout/components/Sidebar/Submenu.vue | 30 ++++ src/layout/components/Sidebar/index.vue | 102 ++----------- src/layout/components/Topbar/Breadcrumbs.vue | 37 +++-- src/layout/components/Topbar/Userinfo.vue | 7 +- src/layout/index.vue | 17 +-- src/main.js | 3 + src/permission.js | 32 ++++ src/router/index.js | 28 +--- src/router/modules/home.js | 22 ++- src/router/modules/user.js | 41 ++++-- src/store/index.js | 6 +- src/store/modules/app.js | 10 +- src/store/modules/login.js | 22 +++ src/store/modules/menu.js | 56 +++++++ 项目开发手册.md | 147 ++++++++++++------- 21 files changed, 467 insertions(+), 240 deletions(-) delete mode 100644 src/api/app.js create mode 100644 src/layout/components/Sidebar/Logo.vue create mode 100644 src/layout/components/Sidebar/Menus.vue create mode 100644 src/layout/components/Sidebar/Submenu.vue create mode 100644 src/permission.js create mode 100644 src/store/modules/login.js create mode 100644 src/store/modules/menu.js 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 @@ + + + + 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 @@ + + 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管理系统
-
- - - - - - 选项1 - 选项2 - - - 选项3 - - - - 选项1 - - - - - - - - - - - - - - - - - - - 选项1 - 选项2 - - - 选项3 - - - + + 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预处理器