上一篇完成了AdminLTE的整合,接下来就要把页面中的逻辑一一填充进来了,先从展示用户信息开始吧。
我们需要用户点击账户信息按钮后被导航到账户信息页。所以需要给账户信息按钮添加router-link,点击时调用router进行页面跳转。
第一步:在账户信息的HTML代码处添加事件
<router-link to="/userProfile/travelCount">
<button href="#" class="btn btn-primary btn-flat ch">账户信息</button>
</router-link>
第二步:新建一个userProfile.vue和一个travelCount.vue。userProfile用来展示用户的基本信息,travelCount用来展示用户的出差记录。travelCount是被嵌套在userProfile中的。
userProfile.vue:
<template>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-3">
<!-- Profile Image -->
<div class="box box-primary">
<div class="box-body box-profile">
<img class="profile-user-img img-responsive img-circle" src="../assets/img/avatar5.png" alt="User profile picture">
<h3 class="profile-username text-center ch">{{displayName}}</h3>
<p class="text-muted text-center ch">{{duty}}</p>
<ul class="list-group list-group-unbordered">
<li class="list-group-item">
<b class="ch">用户名:</b> <b class="pull-right ch">{{displayName}}</b>
</li>
<li class="list-group-item">
<b class="ch">登录名:</b> <b class="pull-right ch">{{name}}</b>
</li>
<li class="list-group-item">
<b class="ch">邮箱地址:</b> <b class="pull-right ch">{{email}}</b>
</li>
<li class="list-group-item">
<b class="ch">所属部门:</b> <b class="pull-right ch">{{department}}</b>
</li>
<li class="list-group-item">
<b class="ch">职务:</b> <b class="pull-right ch">{{duty}}</b>
</li>
<li class="list-group-item">
<b class="ch">办公地点:</b> <b class="pull-right ch">{{location}}</b>
</li>
<li class="list-group-item">
<b class="ch">办公电话:</b> <b class="pull-right ch">{{tel}}</b>
</li>
<li class="list-group-item">
<b class="ch">手机:</b> <b class="pull-right ch">{{phone}}</b>
</li>
<li class="list-group-item">
<b class="ch">上级领导:</b> <b class="pull-right ch">{{superior}}</b>
</li>
</ul>
<strong class="ch"><i class="fa fa-pencil margin-r-5"></i>技能标签</strong>
<p style="padding-top:5px">
<button v-for="skill in skills" class="label btn-primary ch" style="margin:2px; color:white">{{skill}}</button>
</p>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<!-- /.col -->
<div class="col-md-9">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="ch active" data-toggle="tab">
<router-link to="/userProfile/travelCount">
<span>出差统计</span>
</router-link>
</li>
<li class="ch" data-toggle="tab">
<router-link to="/userProfile/workCircle">
<span>我的工作圈</span>
</router-link>
</li>
</ul>
<div class="tab-content">
<router-view></router-view><!--这里是要显示travelCount内容的地方-->
</div>
<!-- /.content -->
</div>
</div>
</div>
</section>
</template>
<script>
export default {
data() {//这里对应用户的基本信息
return {
displayName: null,
duty: null,
name: null,
email: null,
department: null,
location: null,
tel: null,
phone: null,
superior: null,
skills: null
}
},
mounted (){//使用mounted在挂在DOM时通过restful api获取用户基本信息并填充到data中。这个之后详细说明。
this.$http.get(
'https://192.168.227.1:8443/userInfo' ,
{
headers: {'token' : localStorage.token}//在requestHeader中携带之前产生的token用来在后端验证用户权限。
}
)
.then(
//success
response => {
this.displayName = response.data.displayName;
this.duty = response.data.duty;
this.name = response.data.name;
this.email = response.data.email;
this.department = response.data.department;
this.location = response.data.location;
this.tel = response.data.tel;
this.phone = response.data.phone;
this.superior = response.data.superior;
this.skills = response.data.skills;
},
//error
response => {
}
)
}
}
</script>
<style scoped>
@font-face
{
font-family: yaHeiFont;
src: url('../assets/font/yaHei.ttf')
}
.ch
{
font-family:yaHeiFont;
color: black;
}
</style>
第三步,在main.js中引入userProfile.vue文件并且为userProfile添加路由。这里因为userProfile内容要显示的地方是在红框区域内,如下图:
而红框区域内的路由出口是在index.vue中定义的,所以如果想要userProfile的内容正确渲染到红框区域内,则需要把userProfile嵌套在index中,需要用到vue-router的嵌套路由。
// 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 VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import store from './store/store'
//bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap/dist/js/bootstrap.min.js'
//AdminLTE
import './assets/css/skins/_all-skins.min.css'
import './assets/css/AdminLTE.min.css'
import './assets/js/app.min.js'
//font-awesome
import 'font-awesome/css/font-awesome.min.css'
//echarts
import echarts from 'echarts'
//components
import App from './App'
import Login from './components/login'
import Index from './components/index'
import DeviceCatalog from './components/deviceCatalog'
import UserProfile from './components/userProfile'
import TravelCount from './components/travelCount'
import WorkCircle from './components/workCircle'
Vue.use(VueRouter)
Vue.use(VueResource)
//注册echarts的一种方法
// Object.defineProperties(Vue.prototype, {
// $echarts: { get: () => echarts }
// });
//Vue-Resource提交方式设置
Vue.http.options.emulateJSON = true;
const routes = [
//登录页
{
path: '/login',
component : Login
},
//导航页
{
path: '/index',
component: Index,
//导航页子页面,children中的component将被渲染到之前说的红色区域内
children: [
//设备目录页
{
path: '/deviceCatalog',
component: DeviceCatalog
},
//账户信息页
{
path: '/userProfile',
component: UserProfile,
//账户信息子页面
children: [
//出差统计页
{
path: '/userProfile/travelCount',
component: TravelCount
},
//工作圈子页
{
path: '/userProfile/workCircle',
component: WorkCircle
}
]
},
]
},
]
const router = new VueRouter({
routes
})
//默认导航到登录页
// router.push('/login')
/*
全局路由钩子
访问资源时需要验证localStorage中是否存在token
以及token是否过期
验证成功可以继续跳转
失败返回登录页重新登录
*/
router.beforeEach((to, from, next) => {
if(to.path == '/login'){
next()
}
if(localStorage.token && new Date().getTime() < localStorage.tokenExpired){
next()
}
else{
next('/login')
}
})
new Vue({
el: '#app',
template: '<App/>',
components: { App },
router:router,
store: store,
echarts: echarts//注册echarts的另一种方法
}
})
做完之后点击用户信息按钮应该就可以跳转到用户信息页了,看下效果
第四步出差统计信息,这里想使用echarts图表来进行展示(这里我跳了一个坑)。首先在项目中使用npm install echarts –save将echarts下载到项目中。然后在main.js中引用,对应第三步main.js中
import echarts from 'echarts'
在travelCount中使用,travelCount.vue。这里有几点需要注意
1.echarts挂载的dom必须设置一个高度,否则不能显示出来。
2.echarts的配置需要放在mounted中。
3.使用echarts的resize方法配合js中onresize重绘图表以动态适应屏幕尺寸。
<template>
<div id="main" style="height:730px"></div>
</template>
<script>
export default {
mounted() {
var myChart = this.$root.$options.echarts.init(document.getElementById('main'));//这里注意
var option = {
grid : {
left : '1%',
right : '2%',
bottom : '1%',
containLabel : true
},
xAxis : {
type : 'value',
boundaryGap : [ 0, 0.01 ]
},
yAxis : {
type : 'category',
axisLabel : {
inside : true,
textStyle : {
fontWeight : 'bold'
},
},
z : 100,
data : ['A国:2016-01-01至2016-02-01' , 'B国:2016-03-01至2016-04-01' , 'C国:2016-06-01至2016-06-01']
},
series : [ {
type : 'bar',
barGap : '10%',
itemStyle : {
normal : {
color : 'LightSkyBlue'
}
},
data : [100 , 200 , 300]
} ]
}
myChart.setOption(option);
//窗口尺寸变化时重新绘制chart
window.onresize = () => {
myChart.resize()
}
}
}
</script>
这里要说下我跳的坑,引入echarts时,上面的代码使用了
this.$root.$options.echarts
如果这样引用的话,需要在main.js的Vue根实例中注册echarts,见第三步main.js配置。还有一种方法可以使用this.$echarts的方式引用(这个方法是Vue论坛中tomi-li老师指点的,谢谢老师)。
这种方法需要使用js的Object.defineProperties将echarts手动添加到Vue对象中,见第三步main.js配置。使用这种方法时不需要在根实例中注册echarts。
Object.defineProperties(Vue.prototype, {
$echarts: { get: () => echarts }
});
如果没有手动添加的话,在其他Vue文件中使用echarts时会报错,告诉你echarts没有定义过。
看下效果
OK,前端的部分完成了,现在出差信息是静态数据,因为来处理、记录这些数据的服务可能还要依赖其他服务,我们先用静态数据代替。但是用户基本信息是从Ldap中取出来的,之前第二步中不是使用了vur-resourse到这个地址获取用户数据么。
this.$http.get(
'https://192.168.227.1:8443/userInfo' ,
{
headers: {'token' : localStorage.token}
}
)
后端处理这个请求的controller。因为在分布式架构下这个controller通过restful对外提供获取用户信息的服务,所以需要使用@CrossOrigin注解满足跨域的需求。
getUserInfo方法做这几件事情
1.拿到header中的用户token,解密后判断用户凭证是否过期、是否拥有某种权限角色。
2.如果判断没有问题,使用token中的用户名作为入参,调用SpringLdap取得用户信息,并使用EmployeeAttributesMapper 填充我们自己的POJO类。返回response信息。
3.如果判断有问题,返回403表示认证没有通过。
package an.userinfo;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import java.util.Date;
import javax.naming.directory.Attributes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestHeader;
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.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import an.entity.Employee;
import io.jsonwebtoken.Jwts;
@RestController("/userInfo")
public class UserInfoWeb {
//jwt加密密匙
@Value("${jwt.key}")
private String jwtKey;
//ldap模板
@Autowired
private LdapTemplate ldapTemplate;
/**
* 将域用户属性通过EmployeeAttributesMapper填充到Employee类中,返回一个填充信息的Employee实例
*/
private class EmployeeAttributesMapper implements AttributesMapper<Employee> {
public Employee mapFromAttributes(Attributes attrs) throws NamingException, javax.naming.NamingException {
Employee employee = new Employee();
employee.setName((String) attrs.get("sAMAccountName").get());
employee.setDisplayName((String) attrs.get("displayName").get());
employee.setEmail((String) attrs.get("userprincipalname").get());
employee.setDuty((String) attrs.get("title").get());
employee.setDepartment((String) attrs.get("department").get());
employee.setSuperior((String) attrs.get("manager").get());
employee.setLocation((String) attrs.get("physicaldeliveryofficename").get());
employee.setTel((String) attrs.get("homephone").get());
employee.setPhone((String) attrs.get("mobile").get());
String skill = (String) attrs.get("description").get();
String skills[] = skill.split(";");
employee.setSkills(skills);
return employee;
}
}
/**
* 使用用户凭证取得用户信息
* @param token 用户凭证
* @return
*/
@CrossOrigin
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<String> getUserInfo(@RequestHeader("token") String token){
//解析token
String username = Jwts.parser().setSigningKey(jwtKey).parseClaimsJws(token).getBody().getSubject();
String roles = Jwts.parser().setSigningKey(jwtKey).parseClaimsJws(token).getBody().getAudience();
long expiration = Jwts.parser().setSigningKey(jwtKey).parseClaimsJws(token).getBody().getExpiration().getTime();
long current = new Date().getTime();
//验证token过期时间、用户权限
if(current > expiration || roles.indexOf("ROLE_USER") == -1) {
return new ResponseEntity<String>(HttpStatus.FORBIDDEN);
}
//查询并产生用户信息
Employee employee = ldapTemplate
.search(query().where("objectclass").is("person").and("sAMAccountName").is(username),
new EmployeeAttributesMapper())
.get(0);
return new ResponseEntity<String>(JSON.toJSONString(employee , SerializerFeature.DisableCircularReferenceDetect) , HttpStatus.OK);
}
}