element是餓了么團隊開發的PC端用的基於vue的框架,之前在寫app端的時候用的是Mint UI(餓了么團隊)、vux(這個比較好用)。
element官網: https://element.eleme.cn/#/zh-CN
在這里直接下載git上別人寫好的: vue-admin-template
1.下載運行vue-admin-template
git地址: https://github.com/PanJiaChen/vue-admin-template
下載之后進入到項目,執行安裝相關依賴:
npm install --registry=https://registry.npm.taobao.org
運行之后還缺失一些模塊,繼續執行下面即可:
cnpm install
然后運行項目:
npm run dev
運行起來,訪問即可,默認端口是9528:
補充:將該模板漢化。
默認是英語,參考:src/main.js第7行和第32行。如下我們使用日期控件的時候是英語證明當前的語言是英語:
切換漢語,注釋掉src/main.js的第7行和32行並且放開第34行代碼,最終如下:
Vue.use(ElementUI)
2.連接后台項目進行登錄,前后端分離實現登錄(springboot+SSM)
0.去掉一些不必要的js等文件
一開始我直接引入axios的時候發送的數據老是到不了后台,我去掉了其中的一些JS,因為模板本身對axios進行了攔截處理,我去掉之后可以正常發送數據。
1.引入axios
(1)到項目的根目錄下面安裝axios:
cnpm install --save axios
(2)src目錄下新建axios\index.js,內容如下:(所有的請求加一個代理地址,對響應信息過濾處理)
import axios from "axios"; import { MessageBox } from 'element-ui'; // 引入常量模塊 const defaultSettings = require('../settings.js') // 修改axios請求的默認配置(配置會以一個優先順序進行合並。這個順序是:在 lib/defaults.js 找到的庫的默認值,然后是實例的 defaults 屬性,最后是請求的 config 參數。) //` baseURL` 將自動加在 `url` 前面,除非 `url` 是一個絕對 URL。 axios.defaults.baseURL = defaultSettings.serverBasePath; // 添加請求攔截器 axios.interceptors.request.use(function(config) { // 模擬處理前增加token return config; }, function(error) { // 對請求錯誤做些什么 return Promise.reject(error); }); // 添加響應攔截器 axios.interceptors.response.use(function(response) { // 對響應數據做點什么 if(response.data.success) { // 如果是成功返回信息之后提取出來返回以供后面的調用鏈使用(后台返回的JSON數據) return response.data; } else { MessageBox.alert(response.data.msg, "提示信息"); // 返回一個新的Promise對象就相當於接觸鏈式調用 return new Promise(function(resolve, reject) { // resolve('success1'); // reject('error'); }); } }, function(error) { // 對響應錯誤做點什么 return Promise.reject(error); }); export default axios;
(3)修改settings.js加入后台服務基地址
module.exports = { title: '絲綢之路商城', /** * @type {boolean} true | false * @description Whether fix the header */ fixedHeader: false, /** * @type {boolean} true | false * @description Whether show the logo in sidebar */ sidebarLogo: false, /** * 后台服務基地址,每個axios請求都會加這個,攔截請求進行代理 */ serverBasePath: '/api' }
(4)vue.config.js增加代理信息以及引入jquery
'use strict' const path = require('path') const defaultSettings = require('./src/settings.js') const webpack = require("webpack") function resolve(dir) { return path.join(__dirname, dir) } const name = defaultSettings.title || 'vue Admin Template' // page title // If your port is set to 80, // use administrator privileges to execute the command line. // For example, Mac: sudo npm run // You can change the port by the following methods: // port = 9528 npm run dev OR npm run dev --port = 9528 const port = process.env.port || process.env.npm_config_port || 9528 // dev port // All configuration item explanations can be find in https://cli.vuejs.org/config/ module.exports = { /** * You will need to set publicPath if you plan to deploy your site under a sub path, * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, * then publicPath should be set to "/bar/". * In most cases please use '/' !!! * Detail: https://cli.vuejs.org/config/#publicpath */ publicPath: '/', outputDir: 'dist', assetsDir: 'static', lintOnSave: process.env.NODE_ENV === 'development', productionSourceMap: false, devServer: { port: port, open: true, overlay: { warnings: false, errors: true }, proxy: { '/api': { target: 'http://localhost:8088', ws: true, changeOrigin: true, pathRewrite: { '^/api': '' } } } }, configureWebpack: { // provide the app's title in webpack's name field, so that // it can be accessed in index.html to inject the correct title. name: name, resolve: { alias: { '@': resolve('src') } }, plugins: [ new webpack.ProvidePlugin({ jQuery: "jquery", $: "jquery" }) ] }, chainWebpack(config) { config.plugins.delete('preload') // TODO: need test config.plugins.delete('prefetch') // TODO: need test // set svg-sprite-loader config.module .rule('svg') .exclude.add(resolve('src/icons')) .end() config.module .rule('icons') .test(/\.svg$/) .include.add(resolve('src/icons')) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) .end() // set preserveWhitespace config.module .rule('vue') .use('vue-loader') .loader('vue-loader') .tap(options => { options.compilerOptions.preserveWhitespace = true return options }) .end() config // https://webpack.js.org/configuration/devtool/#development .when(process.env.NODE_ENV === 'development', config => config.devtool('cheap-source-map') ) config .when(process.env.NODE_ENV !== 'development', config => { config .plugin('ScriptExtHtmlWebpackPlugin') .after('html') .use('script-ext-html-webpack-plugin', [{ // `runtime` must same as runtimeChunk name. default is `runtime` inline: /runtime\..*\.js$/ }]) .end() config .optimization.splitChunks({ chunks: 'all', cacheGroups: { libs: { name: 'chunk-libs', test: /[\\/]node_modules[\\/]/, priority: 10, chunks: 'initial' // only package third parties that are initially dependent }, elementUI: { name: 'chunk-elementUI', // split elementUI into a single package priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm }, commons: { name: 'chunk-commons', test: resolve('src/components'), // can customize your rules minChunks: 3, // minimum common number priority: 5, reuseExistingChunk: true } } }) config.optimization.runtimeChunk('single') } ) } }
2.修改登錄邏輯
(1)src\views\login\index.vue修改登錄處理(登錄成功之后設置token,並且跳轉路由到桌面。后台返回的是user信息,包括基本信息以及roles角色)
async handleLogin() { // 異步登錄 var response = await axios.post('/doLoginJSON.html', { username: this.loginForm.username, password: this.loginForm.password }); // 登錄成功之后的處理 if(response.success) { // 顯示文字 Message({message: '登錄成功', type: 'success'}); // 將用戶信息作為token存入sessionStorage setToken(response.data); // 跳轉路由 this.$router.replace("/dashboard"); } },
(2)修改src\utils\auth.js中setToken和getToken的方法(原來是存到cookie中,現在我存到sessionStorage中。roles也是后台返回的roles數組信息)。
const TokenKey = 'vue_admin_template_token' export function getToken() { const token = sessionStorage.getItem(TokenKey); if (token) { return JSON.parse(token); } return ""; } export function setToken(token) { if (!token) { return; } // 將用戶存入sessionStorage sessionStorage.setItem("userid", token.id); sessionStorage.setItem("username", token.username); sessionStorage.setItem("fullname", token.fullname); sessionStorage.setItem(TokenKey, JSON.stringify(token)); } export function removeToken() { sessionStorage.removeItem("userid"); sessionStorage.removeItem("username"); sessionStorage.removeItem("fullname"); sessionStorage.removeItem(TokenKey); } export function getRoles() { const rolesArray = []; const token = sessionStorage.getItem(TokenKey); if (token) { rolesArray.push(JSON.parse(token).roles); } return rolesArray; }
(3)后台登錄邏輯如下
/** * 處理登陸請求(JSON數據) * * @param username * @param password * @param session * @return */ @RequestMapping("doLoginJSON") @ResponseBody public JSONResultUtil doLoginJSON(@RequestBody User user, HttpSession session, HttpServletRequest request) { User loginUser = userService.getUserByUserNameAndPassword(user.getUsername(), user.getPassword()); logger.debug("loginUser: {}", loginUser); if (loginUser == null) { return JSONResultUtil.error("賬號或者密碼錯誤"); } session.setAttribute("user", loginUser); return new JSONResultUtil<User>(true, "登錄成功", loginUser); }
3.修改登出邏輯
(1)修改src\layout\components\Navbar.vue
<el-dropdown-item divided @click.native="doLogout"> <span style="display:block;">退出</span> </el-dropdown-item>
登出方法如下:向后台發送登錄請求,跳轉路由之后調用auth.utils的removeToken方法刪掉token信息。
async doLogout() { const logoutUrl = "/logoutJSON.html"; const response = await axios.post(logoutUrl); // 跳轉路由 this.$router.replace("/login"); // 刪除token removeToken(); },
(2)后台springboot登錄邏輯如下:
package cn.qs.controller.system; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import cn.qs.utils.JSONResultUtil; /** * 退出登陸 * * @author Administrator * */ @Controller public class LogoutController { @RequestMapping("logout") public String logout(HttpSession session) { session.removeAttribute("user"); return "redirect:/login.html"; } @RequestMapping("logoutJSON") @ResponseBody public JSONResultUtil logoutJSON(HttpSession session) { session.removeAttribute("user"); return JSONResultUtil.ok(); } }
4. 模板中加入自己的菜單,並且實現權限控制
(1)src\router\index.js文件中constantRoutes里面加入用戶管理菜單:
{ path: '/user', component: Layout, redirect: '/user/list', name: 'user', alwaysShow: true, meta: { title: '用戶管理', icon: 'example', roles: ["系統管理員"] }, children: [ { path: 'list', name: 'List', component: () => import('@/views/user/index'), meta: { title: '用戶列表', icon: 'table' } } ] },
這個有好幾個屬性,在文件頭部也說明了。
/** * Note: sub-menu only appear when route children.length >= 1 * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html * * hidden: true if set true, item will not show in the sidebar(default is false) * alwaysShow: true if set true, will always show the root menu * if not set alwaysShow, when item has more than one children route, * it will becomes nested mode, otherwise not show the root menu * redirect: noRedirect if set noRedirect will no redirect in the breadcrumb * name:'router-name' the name is used by <keep-alive> (must set!!!) * meta : { roles: ['admin','editor'] control the page roles (you can set multiple roles) title: 'title' the name show in sidebar and breadcrumb (recommend set) icon: 'svg-name' the icon show in the sidebar breadcrumb: false if set false, the item will hidden in breadcrumb(default is true) activeMenu: '/example/list' if set path, the sidebar will highlight the path you set } */
簡單解釋幾個有用的:
hidden: 是否隱藏,默認否,隱藏之后不會在左邊菜單欄顯示。
alwaysShow:為true的時候表示只有一個子菜單也顯示父菜單,false會隱藏父菜單,只顯示子菜單。
redirect: 重定向的路由
name:路由名稱。
meta:[
title: '菜單顯示的名稱',
icon:'顯示的圖標'
roles; ['r1', 'r2'] // 需要的權限
]
(2)上面的meta的roles默認沒生效,需要修改src\layout\components\Sidebar\SidebarItem.vue文件:
增加hasRoles(item)方法進行解釋判斷,獲取當前用戶存到sessionStorage的角色信息進行匹配。
<template> <div v-if="!item.hidden && hasRoles(item)"> <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow"> <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)"> <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"> <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" /> </el-menu-item> </app-link> </template> <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body> <template slot="title"> <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" /> </template> <sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child" :base-path="resolvePath(child.path)" class="nest-menu" /> </el-submenu> </div> </template> <script> import path from 'path' import { isExternal } from '@/utils/validate' import Item from './Item' import AppLink from './Link' import FixiOSBug from './FixiOSBug' import { getRoles } from '@/utils/auth' export default { name: 'SidebarItem', components: { Item, AppLink }, mixins: [FixiOSBug], props: { // route object item: { type: Object, required: true }, isNest: { type: Boolean, default: false }, basePath: { type: String, default: '' } }, data() { // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237 // TODO: refactor with render function this.onlyOneChild = null return {} }, methods: { // 根據角色過濾按鈕 hasRoles(item) { if (item && item.meta && item.meta.roles && item.meta.roles.length >0 ) { const userRoles = getRoles(); if (!userRoles) { return false; } var index = 0; for (index in userRoles) { if (item.meta.roles.indexOf(userRoles[index]) > -1) { return true; } } return false; } return true; }, hasOneShowingChild(children = [], parent) { const showingChildren = children.filter(item => { if (item.hidden) { return false } else { // Temp set(will be used if only has one showing child) this.onlyOneChild = item return true } }) // When there is only one child router, the child router is displayed by default if (showingChildren.length === 1) { return true } // Show parent if there are no child router to display if (showingChildren.length === 0) { this.onlyOneChild = { ... parent, path: '', noShowingChildren: true } return true } return false }, resolvePath(routePath) { if (isExternal(routePath)) { return routePath } if (isExternal(this.basePath)) { return this.basePath } return path.resolve(this.basePath, routePath) } } } </script>
實際中還實現了整合vue-kindeditor實現文本編輯器(參考:這個)、分頁查詢商品等操作。
git地址: https://github.com/qiao-zhi/vue_springboot_shop