sixthelement 2020-03-26
element是饿了么团队开发的PC端用的基于vue的框架,之前在写app端的时候用的是Mint UI(饿了么团队)、vux(这个比较好用)。
element官网: https://element.eleme.cn/#/zh-CN
在这里直接下载git上别人写好的: 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)
一开始我直接引入axios的时候发送的数据老是到不了后台,我去掉了其中的一些JS,因为模板本身对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‘) } ) } }
(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); }
(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(); } }
(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实现文本编辑器(参考:这个)、分页查询商品等操作。