mvvm是數據驅動的,數據模型佔了舉足輕重的地位,因此,在作首頁最終要的todo列表組件的時候,先暫時在客戶端使用數據模型進行開發。而既然已經想到了這些數據須要經過交互從服務端獲取,因此這個模型直接放入vuex中,數據模型的代碼上一章已經分析過,因此這裏直接複製過來:css
indexTodos:[ { month:0, //月份 default:1, //正在顯示的月份 todos:[{ createTime:new Date(), //記錄時間 item:'', //標題 content:'', //內容 weather:0, //天氣 mood:0, //心情 bookmark:0, //標記 groupid:0, //所屬組 }] } ]
這裏使用了兩個數組,即月份組,每月是一個項,而月分內呢,記事也是一個數組,每一個記事項就是一個項。html
具體到頁面中怎麼用呢?也就是說,頁面如何使用vuex庫中的值呢?前端
這裏使用computed關鍵字,在vue中computed關鍵字的意思是實時計算,或者說監控值的變化,具體到代碼中,首先須要咱們須要的vuex組件,這裏須要狀態:vue
import { mapState } from 'vuex'
而後使用computed來引用組件中的值:java
computed: mapState({ groupId: state=>state.groupId, items:state=>state.indexTodos }),
這樣,Index.vue就能夠像是使用data節點那樣,經過this引用這兩個值了。git
這裏須要思考一下,head有三個item,每一個item對應的panel都須要在內容部分顯示,那麼,該如何控制具體到每一個panel的顯示或者加載呢?首先pass掉的確定是作三個相同head和foot的頁,這樣的很明顯不符合單頁的需求,第二個被pass掉的應該是在這個頁建立三個div,而後經過tabitem來控制div的隱藏顯示了,那麼,第三種方法應該是第二種的升級版,建立三個組件,經過tabitem來選擇不一樣的組件加載。github
這裏咱們先建立一個components
,用來放咱們所須要的組件。vuex
首先,咱們至少須要三個組件,也就是對應tabitem的三個,分別爲:vue-cli
反正組件文件已經創建,那麼咱們先將他們一股腦的在Index頁面中引用。json
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獲取)和月份(參數傳遞)。
其實根據服務層的方法名,他的僞代碼就都出來了,根據的《代碼大全裏》的方法,用說明註釋寫出來:
註釋簡單,代碼固然也就簡單了:
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的。
接下來回到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循環,如今只走一次,這是爲了以後優化效率,一次性返回多個月而預留的代碼,如今就直接當它是一個順序結構便可.
到目前爲止的代碼:
謝謝觀看