数据模型
mvvm是数据驱动的,数据模型占了举足轻重的地位,所以,在做首页最终要的todo列表组件的时候,先暂时在客户端使用数据模型进行开发。而既然已经想到了这些数据需要通过交互从服务端获取,所以这个模型直接放入vuex中,数据模型的代码上一章已经分析过,所以这里直接复制过来:
indexTodos:[
{
month:0, //月份
default:1, //正在显示的月份
todos:[{
createTime:new Date(), //记录时间
item:'', //标题
content:'', //内容
weather:0, //天气
mood:0, //心情
bookmark:0, //标记
groupid:0, //所属组
}]
}
]
这里使用了两个数组,即月份组,每个月是一个项,而月份内呢,记事也是一个数组,每个记事项就是一个项。
引用vuex
具体到页面中怎么用呢?也就是说,页面如何使用vuex库中的值呢?
这里使用computed关键字,在vue中computed关键字的意思是实时计算,或者说监控值的变化,具体到代码中,首先需要我们需要的vuex组件,这里需要状态:
import { mapState } from 'vuex'
然后使用computed来引用组件中的值:
computed: mapState({
groupId: state=>state.groupId,
items:state=>state.indexTodos
}),
这样,Index.vue就可以像是使用data节点那样,通过this引用这两个值了。
组件化
这里需要思考一下,head有三个item,每个item对应的panel都需要在内容部分显示,那么,该如何控制具体到每个panel的显示或者加载呢?首先pass掉的肯定是做三个相同head和foot的页,这样的很明显不符合单页的需求,第二个被pass掉的应该是在这个页创建三个div,然后通过tabitem来控制div的隐藏显示了,那么,第三种方法应该是第二种的升级版,创建三个组件,通过tabitem来选择不同的组件加载。
这里我们先创建一个components
,用来放我们所需要的组件。
首先,我们至少需要三个组件,也就是对应tabitem的三个,分别为:
- DiaryPanel.vue 记录项(为防止与日记记录组相混淆,这里统一改为记录,标题为点滴,略微文青些)
- Calendar.vue 日历项
- Mine.vue 我的项
反正组件文件已经建立,那么我们先将他们一股脑的在Index页面中引用。
import DiaryPanelComponents from '../components/DiaryPanel.vue'
import CalendarComponents from '../components/Calendar.vue'
import MineComponents from '../components/Mine.vue'
因为完成之后,紧接着就是要对它们进行注册:
components:{
DiaryPanelComponents,
CalendarComponents,
MineComponents
},
这时,就可以和html标签一样使用了。
<div id="contentPanel">
<transition name="custom-classes-transition"
enter-active-class="animated bounceInLeft"
leave-active-class="animated fadeOut"
:duration="{ enter: 700, leave: 200 }"
>
<DiaryPanelComponents></DiaryPanelComponents>
</transition>
</div>
但是,我们想想,这样的这个页面只能使用DiaryPanelComponents
这一个组件,其他组件怎么办,如果将三个组件一股脑的全写在这个div节点中,控制显示隐藏,岂不是又回到了老路上?
好在vue提供了动态绑定组件的功能,我们在data数据模型中新增一个表示组件名称的属性currentView
表示当前处于显示状态的组件:
data () {
return {
currentView:'DiaryPanelComponents',
...
}
},
然后修改组件部分的模板html:
<div id="contentPanel">
<transition name="custom-classes-transition"
enter-active-class="animated bounceInLeft"
leave-active-class="animated fadeOut"
:duration="{ enter: 700, leave: 200 }"
>
<component v-bind:is="currentView">
</component>
</transition>
</div>
这样,tab的item选择操作,就变成了最基本的的字符串赋值操作:
tabChange:function(event){
...
var componentName = ''
switch (event) {
case 'tab1':
componentName = 'DiaryPanelComponents'
break
case 'tab2':
componentName = 'CalendarComponents'
break
case 'tab3':
componentName = 'MineComponents'
break
}
this.currentView = componentName
}
组件嵌套
首页现在基本只起一个调度作用,具体的内容交给了组件来完成,下面打开DiaryPanel.vue,对这个组件进行开发。
分析一下这个组件,这个组同样分为两部分,头部一个作为标题的月份,下边循环显示一个此月所有的记录项。
但无论开发哪个部分,我们都需要先从vuex中将数据取出来:
import { mapState } from 'vuex'
export default {
computed: mapState({
indexTodos: state=>state.indexTodos,
})
}
剩下的就很简单了,先把显示的部分代码写完,这里用了museui的组件sub-header:
<div v-for="item in indexTodos" >
<mu-sub-header class="day_title">{{ item.month }}</mu-sub-header>
<DiaryListComponents></DiaryListComponents>
</div>
然后根据实际情况修改css样式:
.day_title{
font-size: 50px;
line-height: 55px;
font-family: 'Microsoft YaHei',arial,tahoma,\5b8b\4f53,sans-serif;
font-weight: 500;
color: #5e35b1;
text-align: center;
padding: 0px;
}
接下来就是循环显示记录列表了,想一下原型中,这个todo放到了一个面板块内,而面板块还是比较复杂的,并且每个月都要使用,所以,我们也把他提炼到一个组件中,嗯,就叫DiaryList,从这里也可以看出,vue的组件是支持嵌套的。接下来在components文件夹内创建DiaryList文件。
同时,由于用户会滑动页面,也就是说,这个组件内所需要的值,即todo数组,是与父组件联系紧密的,所以需要通过参数的方式,将父组件循环得来的值传送到子组件中,vue中传值也非常方便,在标签引用的地方绑定一下就行了:
<DiaryListComponents v-bind:todos="item.todos"></DiaryListComponents>
然后子组件获取更加简单:
DiaryList.vue:
export default {
props:["todos"]
}
这样就可以直接使用todos变量。
而面板使用museui的pager控件就可以了,还自带阴影效果,并且是在循环体内,使用todos变量的pager代码如下;
<mu-paper class="diaryitem" :zDepth="2" v-for="(item) in todos" >
</mu-paper>
过滤器
接下来就是对这个组件的开发了。观察一下这个块的内容:
首先四周都有个边框,所以用一个父级的mu-content-block包裹一下,然后看内容,是一个左中右的结构,刚好museui有个布局表格,就直接使用了,布局表格的权重,暂时就20-6-20吧,最终布局部分代码如下:
<mu-paper class="diaryitem" :zDepth="2" v-for="(item) in todos" >
<mu-content-block>
<mu-row gutter>
<mu-col width="20">
</mu-col>
<mu-col width="60">
</mu-col>
<mu-col width="20" style="text-align:right">
</mu-col>
</mu-row>
</mu-content-block>
</mu-paper>
剩下的内容,如果先不考虑样式的话,最简单应该就是标题和内容了,直接输入就好:
<mu-col width="60">
<div class="item_time">12:34</div>
<div class="item_title">{{ item.item }}</div>
<div class="item_content">{{item.content}}</div>
</mu-col>
css的一会在完善,接下来就是时间了,其实时间虽然现实了这么多,但是具体到了数据项上,实际上只有一个,就是createtime,接下来要做的就是如何提取显示的问题了,这时候vue提供的过滤器就登场了,下面以日期为例介绍一下过滤器的用法.
过滤器其实就是一个通过filter标记的普通js的方法,然后我们先让他返回一个固定数字的写法:
filters: {
getDay(time) {
return 3;
}
}
调用方法为:
{{ item.createTime | getDay }}
其中item.createTime对应模型中的值和过滤器方法的参数,getDay很明显,就是我们过滤器的方法了。有了这些,完成过滤器就很简单了:
getDay(time) {
var date = new Date(time);
return date.getDate();
}
这时页面上就回只显示日期值的。
接下来我们想到,不只是日期需要,其他的需要的还有很多,比如月份,时间等,而且在可预见的地方,比如新增记录页,tag的列表页等,所以这个功能有必要提取复用一下,关于日期操作的js方法网上有很多,就不在叙述,这个作为工具类,通服务端代码一样,创建一个util文件夹,然后就叫Date.js文件,最终的代码如下:
export function formatDate(time, fmt) {
var date = new Date(time);
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
let o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'w+':getWeek(date)
};
for (let k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
let str = o[k] + '';
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
}
}
return fmt;
};
function padLeftZero(str) {
return ('00' + str).substr(str.length);
}
function getDay(time){
return formatDate(time,"dd");
}
function getWeek(time){
var weekName=['星期日','星期一','星期二','星期三','星期四','星期五','星期六']
return weekName[time.getDay()];
}
function getTime(time){
return formatDate("hh-mm-dd");
}
然后DirayList组件内引入,并完成剩余的几个过滤器方法:
<script>
import { formatDate } from '../utils/date.js';
export default {
props:["todos"],
filters: {
getDay(time) {
return formatDate(time,"dd");
},
getWeek(time) {
return formatDate(time,"w");
},
getTime(time) {
return formatDate(time,"hh:mm");
}
}
}
</script>
最后,是右边的三个icon图标,这三个db中存储的是int型,而页面显示需要一个String的name,所以,通date中的week一样,分别将int作为数组的下标,这里给出三个最简的形式:
mood.js
export function mood(num) {
var moodValue=["mood",""]
if(num==null)
num=0;
return moodValue[num];
}
weather.js
export function weather(num) {
var weatherValue=["wb_sunny",""]
if(num==null)
num=0;
return weatherValue[num];
}
bookmark.js
export function bookmark(num) {
var bookmarkValue=["bookmark_border","bookmark"]
if(num==null)
num=0;
return bookmarkValue[num];
}
最终标签内的代码如下:
<mu-paper class="diaryitem" :zDepth="2" v-for="(item) in todos" >
<mu-content-block>
<mu-row gutter>
<mu-col width="20">
<div class="item_day">{{ item.createTime | getDay }}</div>
<div class="item_week">{{ item.createTime | getWeek }}</div>
</mu-col>
<mu-col width="60">
<div class="item_time">{{ item.createTime | getTime }}</div>
<div class="item_title">{{ item.item }}</div>
<div class="item_content">{{item.content}}</div>
</mu-col>
<mu-col width="25" style="text-align:right">
<mu-icon :value=" item.mood | getMoodValue " :size="16"/>
<mu-icon :value=" item.weather | getWeatherValue " :size="16"/>
<mu-icon :value=" item.bookmark | getBookmarkValue " :size="16"/>
</mu-col>
</mu-row>
</mu-content-block>
</mu-paper>
js代码如下:
import { formatDate } from '../utils/date.js';
import { mood } from '../utils/mood.js';
import { weather } from '../utils/weather.js';
import { bookmark } from '../utils/bookmark.js';
export default {
props:["todos"],
filters: {
getDay(time) {
var date = new Date(time);
console.log(date)
return date.getDate();
//return formatDate(time,"dd");
},
getWeek(time) {
return formatDate(time,"w");
},
getTime(time) {
return formatDate(time,"hh:mm");
},
getMoodValue(num){
return mood(num);
},
getWeatherValue(num){
return weather(num);
},
getBookmarkValue(num){
return bookmark(num);
}
}
}
css代码略,请自行查看源码
这时候跑起来,效果如图:
从当前的界面莱克,基本上符合原型的要求。
服务端数据
剩下的内容就简单了,只要解决数据来源的问题就清楚了,我们在贴一下要求的数据格式:
indexTodos:[
{
month:0, //月份
default:1, //正在显示的月份
todos:[{
createTime:new Date(), //记录时间
item:'', //标题
content:'', //内容
weather:0, //天气
mood:0, //心情
bookmark:0, //标记
groupid:0, //所属组
}]
}
]
同时,还需要一个itemnumber,所以回到服务端的java代码,一步一步的完成这个api功能。
首先,为了和原有的代码区分,新创建一个ApiTodoController控制器,里边新增一个action,apiIndex,这个action除了token外,还需要一个月份作为参数,这个也很容易理解。然后我们需要根据月份查询todo列表,在之前还提到过,由于分多个组,需要设置一个默认组,首页显示默认组的todo,所以,服务层的方法名也就出来了,getTodosByDefaultGroup,参数有两个,用户Id(由token获取)和月份(参数传递)。
其实根据服务层的方法名,他的伪代码就都出来了,根据的《代码大全里》的方法,用说明注释写出来:
- 根据用户id查询此用户的默认记录组
- 查询此组此月的所有记录
注释简单,代码当然也就简单了:
public List<Todo> getTodoByDefaultGroup(int userId,int month) {
TodoGroup todoGroup=todoGroupRepository.findByIsDefaultAndUserId(1,userId);
DateBetween between=getDate(month);
List<Todo> todos= todoRepository.getByGroupIdAndCreateTimeBetween(todoGroup.getId(),between.getFirstDate(),between.getEndDate());
return todos;
}
repository层内只有方法名没有方法体,所以查看调用就能看到全部内容,不在叙述。
DateBetween类从名字就可以看出来,表示一个日期区间,具体到这个代码中,表示的是这个月的1号到这个月的最后一天,即31号(1月份),他的代码如下:
class DateBetween{
private Date firstDate;
private Date endDate;
//get set
}
getDate就是获取参数月的起始和结束日期,代码如下:
private DateBetween getDate( int month ){
DateBetween between=new DateBetween();
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Calendar firstCalender = Calendar.getInstance();
// 获取前月的第一天
firstCalender = Calendar.getInstance();
firstCalender.add(Calendar.MONTH, 0);
firstCalender.set(Calendar.DAY_OF_MONTH, 1);
between.setFirstDate(firstCalender.getTime());
// 获取前月的最后一天
Calendar endCalender = Calendar.getInstance();
endCalender.add(Calendar.MONTH, 1);
endCalender.set(Calendar.DAY_OF_MONTH, 0);
between.setEndDate(endCalender.getTime());
return between;
}
貌似有点啰嗦,先这样回头再慢慢重构吧,这个方法只有这个类用,是private的。
##组装json##
接下来回到Controller,这里没什么好说的,jackson
库能直接将Map和类转成Json对象,所以直接把前端需要的数据通过map组装起来就好了,直接贴代码:
@RequestMapping(value = "/api/index",method = RequestMethod.POST)
public Object apiIndex(HttpServletRequest request,@RequestBody Map map){
//获取首页数据
String userId=request.getAttribute("tokenId").toString();
Integer month=Integer.parseInt( map.get("month").toString());
List<Map<String,Object>> items=new ArrayList<Map<String,Object>>();
for (int i=0;i<1;i++) {
List<Todo> todos = todoService.getTodoByDefaultGroup(Integer.parseInt(userId),month);
//数据结构扩充接口
Map<String, Object> data = new HashMap<String, Object>();
data.put("month",month);
data.put("todos",todos);
data.put("default",1);
items.add(data);
}
Map<String,Object> resutl=new HashMap<String,Object>();
resutl.put("items",items);
resutl.put("itemnumber",items.size());
return result(resutl);
}
注意这个for循环,现在只走一次,这是为了之后优化效率,一次性返回多个月而预留的代码,现在就直接当它是一个顺序结构即可.
到目前为止的代码:
谢谢观看