• 如果你想开发一个应用(1-17)


    数据模型

    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,5b8b4f53,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循环,现在只走一次,这是为了之后优化效率,一次性返回多个月而预留的代码,现在就直接当它是一个顺序结构即可.

    到目前为止的代码:

    前端vue
    后端java

    谢谢观看

  • 相关阅读:
    随时积累随手记(持续更新...)
    Zookeeper集群搭建
    Dubbo——基于Zookeeper服务框架搭建及案例演示
    nginx配置浅析
    阿里面试回来,想和Java程序员谈一谈
    博客收藏列表
    启示录:打造用户喜爱的产品
    js深拷贝和浅拷贝
    MyBatis 总结记录
    ActiveMQ初体验
  • 原文地址:https://www.cnblogs.com/jiangchao226/p/8251900.html
Copyright © 2020-2023  润新知