前段时间学习了vue.js的一些相关知识。现在动手敲代码,想实现一个简单的后台管理,包括登录验证、菜单导航、列表、增删改查,菜单/按钮的权限控制等一些常见功能。当然网上也有很多例子了,只是想自己敲一遍代码。一直从事后台开发,公司都是有专门的前端设计及开发的,所以本人的js/css基础非常差,只能随意弄弄了。
网上找了下,这篇博文给的例子非常不错http://www.cnblogs.com/fhen/p/6721930.html,也主要参考了下界面的实现,他的数据交互模拟是用了mock.js,本人作为后台开发专业程序员,所以学习过程中,会使用spring boot快速创建一些api支持。
使用vue-cli创建项目,准备工作及创建方法就不多说了,前面有相关博文了http://blog.csdn.net/dream_broken/article/details/73293391
创建个项目admin-demo-01,这是分步骤学习的,01是登录,后面还有02,03....慢慢的一点点功能实现。
看package.json
已经默认有vue,vue-router了,由于要使用axios作为和后台的交互(就像jquery的ajax),element-ui作为控件,所以需要安装axios/element-ui,执行命令
cnpm install axios --save
cnpm install element-ui --save
然后初始化下把整个项目都初始化npm install,然后运行起来cnpm run dev,浏览器访问
新创建的项目,运行后访问看到就是上面的那图,我们需要去掉它,同时显示我们的登录页面,打开App.vue,把img那去掉。
main.js修改为
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-default/index.css'
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: ' ',
components: { App }
})
接着,在components下创建一个Login.vue.
{{ msg }}
router下的index.js初始是这样的
import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Hello',
component: Hello
}
]
})
它默认指向components/Hello,那我们修改下
import Vue from 'vue'
import Router from 'vue-router'
// 懒加载方式,当路由被访问的时候才加载对应组件
const Login = resolve => require(['@/components/Login'], resolve)
Vue.use(Router)
export default new Router({
routes: [{
path: '/',
name: '登录',
component: Login
}]
})
页面就刷新如下图了
那接下来就改造登录页:账号输入框、密码输入框、登录按钮,Login.vue修改如下
系统登录
记住密码
登录
页面刷新
这时点击登录还没有交互能力的。
上面说了axios类似jquery的ajax。在src下建立个api文件夹,然后建立个api.js
import axios from 'axios'
axios.defaults.baseURL = 'http://127.0.0.1:80';
export const requestLogin = params => { return axios.post('/user/login', params).then(res => res.data) }
然后再建立个index.js
import * as api from './api'
export default api
在src目录下创建文件夹mock,创建文件index.js
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
import { LoginUsers, users } from './data/user'
export default {
init() {
let mock = new MockAdapter(axios)
// mock success request
mock.onGet('/success').reply(200, {
msg: 'success'
})
// mock error request
mock.onGet('/error').reply(500, {
msg: 'failure'
})
// 登录
mock.onPost('/user/login').reply(arg => {
let { username, password } = JSON.parse(arg.data)
return new Promise((resolve, reject) => {
let token = null
let hasUser = LoginUsers.some(u => {
if (u.username === username && u.password === password) {
token = 'adminXXXXXX'
return true
}
})
if (hasUser) {
resolve([200, { code: 200, msg: '请求成功', token: token }])
} else {
resolve([200, { code: 500, msg: '账号或密码错误' }])
}
})
})
}
}
mock下建立data文件夹,data文件夹下创建user.js
/**
* 用来模拟用户的一些信息
*/
import Mock from 'mockjs'
const LoginUsers = [{
id: 1,
username: 'admin',
password: '123456',
email: '[email protected]',
name: '程序员'
}]
export { LoginUsers, users }
系统登录
记住密码
登录
看到登录成功后,是要跳到后台主界面Home的,所以创建个Home.vue
{{ msg }}
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
//import Login from '@/components/Login'
// 懒加载方式,当路由被访问的时候才加载对应组件
const Login = resolve => require(['@/components/Login'], resolve)
Vue.use(Router)
let router = new Router({
routes: [{
path: '/',
name: '登录',
component: Login
}, {
path: '/login',
name: '登录',
component: Login
},
{
path: '/home',
name: '后台主界面',
component: Home
}
]
})
// 访问之前,都检查下是否登录了
router.beforeEach((to, from, next) => {
// console.log('to:' + to.path)
if (to.path.startsWith('/login')) {
window.sessionStorage.removeItem('access-token')
next()
} else {
let token = window.sessionStorage.getItem('access-token')
if (!token) {
next({ path: '/login' })
} else {
next()
}
}
})
export default router
在main.js中引入mock
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-default/index.css'
import Mock from './mock'
Mock.init()
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
template: ' ',
components: { App }
})
cnpm install mockjs --save
cnpm axios-mock-adapter
最后运行cnpm run dev的时候报错
babel-runtime/core-js/json/stringify in ./src/mock/index.js, ./~/[email protected]@babel-loader/lib!./~/[email protected]@vue-loader/lib/selector.js?typ是使用了JSON引起的,安装提示执行cnpm install babel-runtime --save
最后在package.json中看到的样子
src下的文件结构
运行起来cnpm run dev
出现登录界面,如果如果的用户不正确(不再mock/data/user.js的LoginUsers中,则会提示账号或密码错误
如果正确,就会进入后台主界面,如果没登录,直接访问主界面的url http://localhost:8080/home,则会自动刷新到登陆页面
上面的代码,当登录的时候,发起登录请求/user/login的时候,被axios-mock-adapter拦截了,并没有正真和后台交互,这种模式适合纯粹的前端开发阶段,但最后还是要和后台联调的。
main.js中去掉
//import Mock from './mock'
//Mock.init()
再次登陆的时候,就无法登录了,看浏览器控制台就看到404了
现在使用eclipse写个后台的用户管理user-server,使用spring boot快速搭建。
UserController.java
package com.fei.springboot.controller;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.fei.springboot.controller.util.TokenUtil;
@RestController
@RequestMapping("/user")
public class UserController {
private static Logger log = LoggerFactory.getLogger(UserController.class);
@CrossOrigin(origins="*")//允许跨域请求
@RequestMapping(value="/login",method=RequestMethod.POST)
public JSONObject login(String username,String password,HttpServletRequest request){
log.info("登录请求...username="+username+" pwd=" + password);
JSONObject r = new JSONObject();
if("admin".equals(username) && "123456".equals(password)) {
r.put("code", "200");
r.put("msg", "登录成功");
r.put("token", TokenUtil.getToken(username));
}else{
r.put("code", "500");
r.put("msg", "登录失败");
}
return r;
}
}
好了,一切准备就绪了,点击“登录”,但是提示登录失败了,看后台控制台日志,接收到的用户名和密码是null。。。这是怎么回事?使用postman工具测试下接口,访问是OK的。。。赶紧百度找原因,在这篇博文中,解说得非常详细http://www.jianshu.com/p/042632dec9fb,原因找到了,解决方法有3中。按照那博文的说法,原因就是axios判断参数是object对象时,header中的Content-type是application/json;charset=UTF-8,而springMVC是默认application/x-www-form-urlencoded;charset=UTF-8,平时我们用jquery的ajax的时候,默认就是application/x-www-form-urlencoded;charset=UTF-8了,和后台匹配,所以我们都不需要额外做处理。看下axios.js的源码
如果参数是Object,而且headers中没有设置contentType,那就默认'application/json;charset=utf-8',把Object转为json字符串,那也就是说,如果使用axios.post的时候指定了headers的content为application/x-www-form-urlencoded;charset=UTF-8就可以了呢?试了下
api.js修改前
export const requestLogin = params => { return axios.post('/user/login', params).then(res => res.data) }
发起请求
let config = { headers: { 'content-type': 'application/x-www-form-urlencoded;charset=UTF-8' } }
export const requestLogin = params => { return axios.post('/user/login', params, config).then(res => res.data) }
request headers 中的content-type的确是变了,但是后台仍然没接收到数据。用postman测试
成功,查看发送的信息
发现是参数,axios是json字符串,而postman那是&拼接起来的。
那只能要么后台接收参数的时候使用@RequestBody,要么前台使用URLSearchParams,
试试URLSearchParams,Login.vue,修改下
// var loginParams = { username: this.account.username, password: this.account.pwd };
let loginParams = new URLSearchParams();
loginParams.append("username",this.account.username);
loginParams.append("password",this.account.pwd);
googel浏览器OK,但是360浏览器却报Uncaught ReferenceError: URLSearchParams is not defined,网上的一些说法是
URLSearchParams,并不是所有浏览器都支持的。
修改后台UserController.java
package com.fei.springboot.controller;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.fei.springboot.controller.util.TokenUtil;
@RestController
@RequestMapping("/user")
public class UserController {
private static Logger log = LoggerFactory.getLogger(UserController.class);
@CrossOrigin(origins="*")//允许跨域请求
@RequestMapping(value="/login",method=RequestMethod.POST)
public JSONObject login(@RequestBody Map map){
String username = map.get("username");
String password = map.get("password");
log.info("登录请求...username="+username+" pwd=" + password);
JSONObject r = new JSONObject();
if("admin".equals(username) && "123456".equals(password)) {
r.put("code", "200");
r.put("msg", "登录成功");
r.put("token", TokenUtil.getToken(username));
}else{
r.put("code", "500");
r.put("msg", "登录失败");
}
return r;
}
}
完整源码