美文网首页Java 杂谈Vue.js程序员
如果你想开发一个应用(1-17)

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

作者: 双鱼座的牛 | 来源:发表于2018-01-09 16:04 被阅读0次

    数据模型

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

    到目前为止的代码:

    前端vue
    后端java

    谢谢观看

    相关文章

      网友评论

        本文标题:如果你想开发一个应用(1-17)

        本文链接:https://www.haomeiwen.com/subject/jiqgnxtx.html