實現(xiàn)項目
這個項目和微吉風(fēng)很像惧眠,不過它是小程序,我這里的是webapp,哈哈于个。其實我這這個項目就是玩玩氛魁,為了練習(xí)練習(xí)vue而來,積累一下前端如何前后端分離厅篓,感受一下vue的美麗秀存,擁抱一下Muse-UI。
接口設(shè)計
用flask寫的接口api在做這個前端項目的時候有調(diào)整過羽氮,接口主要有:
- 登陸:login
- 鑒證:auth
- 查詢分數(shù):queryScore
- 查詢課表:querySchedule
- 獲取周數(shù):getCurrentWeek
- 獲取個人學(xué)年時間:getStuTimeLines
實現(xiàn)
1.數(shù)據(jù)來源:模擬登陸:學(xué)校教務(wù)系統(tǒng)官網(wǎng)
2.數(shù)據(jù)簡化:模擬登陸的過程是在服務(wù)器完成的或链,請求的到數(shù)據(jù)通過服務(wù)器進行刷選后簡化數(shù)據(jù),再返回給瀏覽器档押。幾乎所有的請求都是要模擬登陸一次學(xué)校教務(wù)系統(tǒng)澳盐,再請求數(shù)據(jù)的,數(shù)據(jù)都是不會保存到服務(wù)器的令宿。
3.數(shù)據(jù)更新周期:分析返回來的數(shù)據(jù)叼耙,會發(fā)現(xiàn)有些數(shù)據(jù)變動非常小,如果每次打開頁面都向服務(wù)器發(fā)起請求,就太消耗服務(wù)器資源了掀淘。比如課表都幾乎是一學(xué)期才會有變動的旬蟋,頻繁請求就有點多余。但是這里設(shè)計的服務(wù)器是不打算保存任何與用戶相關(guān)的信息的革娄,所以這里用到了WebStorage的儲存方案倾贰,localStorage(目前主流的瀏覽都支持)使用localStorage來緩存這些變化不大的數(shù)據(jù),比如課表數(shù)據(jù)拦惋,周數(shù)匆浙,個人學(xué)年時間等。
拓展:只讀的
localStorage
屬性允許你訪問一個Document
源(origin)的對象Storage
厕妖;其存儲的數(shù)據(jù)能在跨瀏覽器會話保留首尼。localStorage
類似sessionStorage
,但其區(qū)別在于:存儲在localStorage
的數(shù)據(jù)可以長期保留言秸;而當(dāng)頁面會話結(jié)束——也就是說软能,當(dāng)頁面被關(guān)閉時,存儲在sessionStorage
的數(shù)據(jù)會被清除 举畸。應(yīng)注意查排,無論數(shù)據(jù)存儲在
localStorage
還是sessionStorage
,它們都特定于頁面的協(xié)議抄沮。另外跋核,
localStorage
中的鍵值對總是以字符串的形式存儲。 (需要注意, 和js對象相比, 鍵值對總是以字符串的形式存儲意味著數(shù)值類型會自動轉(zhuǎn)化為字符串類型).語法:
//設(shè)置值 localStorage.setItem('name', 'Cendeal'); //獲取值 localStorage.getItem('name'); //或者這獲取值 localStorage.name //刪除值 localStorage.removeItem('name'); //或者也可以這樣刪除值 delete localStorage.name // 移除所有 localStorage.clear();
下面是計算數(shù)據(jù)存活周期:
/**
* 計算生存時間
* @param Date now-當(dāng)前時間
* @param Number day-天數(shù)
* @return Number
*/
function calculateExpiration(now,day) {
let future = new Date()
future.setHours(0,0,0,0)
future.setDate(now.getDate()+day)
return Math.floor(future - now)
}
/**
* 獲取當(dāng)前周的生存時間
* @return Number 生存時間-秒
*
*/
function getCurrentWeekExpiration() {
let now = new Date()
return calculateExpiration(now,8-now.getDay())
}
/**
* 獲取課表的生存周期
* @param Number totalWeek-總周數(shù)
* @return Number 生存時間-秒
*/
function getScheduleExpiration(totalWeek=22) {
let now = new Date()
return calculateExpiration(now,totalWeek*7)
}
export {getCurrentWeekExpiration,getScheduleExpiration}
4.改造localStorage:了解到localStorage儲存室沒有生命周期的叛买,它依賴瀏覽器的清除規(guī)則砂代,一般都是需要用戶手動清除數(shù)據(jù)的,那么要怎樣才知道課表需要更新數(shù)據(jù)呢率挣,除了用戶自己手動更新外刻伊,還需要設(shè)置一個更新周期,明顯一般都是學(xué)期末據(jù)需要更新數(shù)據(jù)了椒功,那么就可以考慮為其設(shè)置存活時間娃圆。由于localStorage是沒有這函數(shù)的,所以這里重寫了一個蛾茉,為localStorage添加存活時間讼呢。
方案是這樣的:每次存新鍵值時,先計算這個數(shù)據(jù)可以存儲多久谦炬,然后把時間和鍵值一起存進去悦屏,當(dāng)取值的時候就判斷當(dāng)前時間是否超過了那個鍵值的存活時間,如果超過了就delete它键思,并返回null或undefinded
代碼如下:
class JluzhLocalStorage {
//獲取值
getItem(key) {
let now = new Date()
let data = JSON.parse(localStorage.getItem(key))
if (data != null) {
if (data.expiration != null) {
let expiration = new Date(data.expiration)
if (expiration - now <= 0) {
delete localStorage['key']
return undefined
}
}
return data.val
} else {
return null
}
}
//默認無限時間
setItem(key, val, expiration = null) {
let time = null
if (expiration != null) {
let now = new Date()
time = now.setSeconds(now.getSeconds() + expiration)
}
let data = {val: val, expiration: time}
localStorage.setItem(key, JSON.stringify(data))
}
//清空
clear() {
localStorage.clear()
}
//獲取長度
get length() {
return localStorage.length
}
get info() {
let data = {
size: 0,
count: localStorage.length,
keys: []
}
let temp = 0
for (let i in localStorage) {
temp += 1
if (temp > data.count) {
break
}
let blob = new Blob([localStorage[i]])
data.size += blob.size
data.keys.push(i)
}
return data
}
}
const jluzhLocalStorage = new JluzhLocalStorage()
export default jluzhLocalStorage
5.主題樣式保存方案:這里主題保存樣式也是利用了localStorage進行保存础爬,服務(wù)器不會同步,清掉緩存就會恢復(fù)默認主題吼鳞。sessionStorage我用來保存課表課程的顏色塊數(shù)據(jù)看蚜,所以每次打開新的會話顏色都變化。
6.主題狀態(tài)統(tǒng)一:這里使用的是vuex赔桌,通過store.js里的state保存主題的樣式供炎,更新使用mutations里面定義的函數(shù)進行更新渴逻。
代碼:
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
// noinspection JSValidateTypes
const store = new Vuex.Store({
state: {
app_title: '吉機',
app_host: 'http://www.cendeal.cn:5001/jlu/api',
theme: {
nav_style: {
backgroundColor: 'rgb(244, 67, 54)',
color: 'white',
'z-index': 999
},
nav_active_color: 'green',
head_pic_style: {
backgroundColor: '#ffca28',
'position': 'relative',
'top': '8px'
},
head_text_style: {
'color': '#7cb342'
},
float_btn_style: {
'bg': 'red',
'text': 'yellow'
}
},
url_paths: {
u_login: '/login',
u_schedule: '/querySchedule',
u_score: '/queryScore',
u_week: '/getCurrentWeek',
u_lines: '/getStuTimeLines',
u_auth: '/auth',
u_logout: '/logout'
},
jluzh_courses: '',
current_week:1,
},
getters: {
navStyle: state => {
return state.theme.nav_style
},
barColor: state => {
return state.theme.nav_style.backgroundColor
},
urlPaths: state => {
return state.url_paths
},
headPicStyle: state => {
return state.theme.head_pic_style
},
headTextStyle: state => {
return state.theme.head_text_style
},
theme: state => {
return state.theme
}
},
mutations: {
updateTheme: function (state, theme) {
if (theme.hasOwnProperty('float_btn_bg'))
state.theme.float_btn_style.bg = theme.float_btn_bg
if (theme.hasOwnProperty('float_btn_text_color'))
state.theme.float_btn_style.text = theme.float_btn_text_color
if (theme.hasOwnProperty('nav_active_color'))
state.theme.nav_active_color = theme.nav_active_color
if (theme.hasOwnProperty("nav_bg"))
state.theme.nav_style.backgroundColor = theme.nav_bg
if (theme.hasOwnProperty('nav_text_color'))
state.theme.nav_style.color = theme.nav_text_color
if (theme.hasOwnProperty('head_pic_bg'))
state.theme.head_pic_style.backgroundColor = theme.head_pic_bg
if (theme.hasOwnProperty('head_pic_text_color'))
state.theme.head_text_style.color = theme.head_pic_text_color
},
initTheme:function () {
let theme = localStorage.getItem('jluzh_theme')
if(theme != null || theme != undefined){
store.commit('updateTheme',JSON.parse(theme))
}
},
updateWeek:function (state,week) {
state.current_week = week
},
updateCourses:function (state,courses) {
if(courses==null)
delete localStorage['jluzh_courses']
state.jluzh_courses = courses
}
},
actions: {}
})
export default store
7.跨域問題:因為后端和前端部署不是在同一個域下的,后端是通過gunicorn部署在5001端口音诫,前端直接使用的nginx監(jiān)聽的80端口,所以當(dāng)前端發(fā)起數(shù)據(jù)請求時就會出現(xiàn)跨域惨奕,請求會被瀏覽器攔截。為了解決這個問題竭钝。這里使用的是vue-jsonp發(fā)起跨域請求(其原理就是js文件的請求是可以跨域的)【這里處理的不是太好梨撞,還是存在一點問題,其實比較簡單的就是采用代理的方法】
/**
* created by cendeal 2019/3/7
* 通過vue-jsonp 進行跨域請求數(shù)據(jù)
* 所有函數(shù)返回的都是promise對象
* 要求:全局使用vue-jsonp,vuex
*/
// 登陸
function loginjs(token) {
return this.$jsonp(this.$store.state.app_host + this.$store.getters.urlPaths.u_auth, {
token: token,
callbackName: 'jsonpCallback'
})
}
// 獲取選項
function getSelection() {
return this.$jsonp(this.$store.state.app_host + this.$store.getters.urlPaths.u_lines,
{callbackName: 'jsonpCallback'})
}
// 獲取分數(shù)
function getScore(grade, term) {
return this.$jsonp(this.$store.state.app_host + this.$store.getters.urlPaths.u_score,
{
Grade: grade,
term: term,
callbackName: 'jsonpCallback'
})
}
// 獲取課表
function getScheduleJs(Grade = null, term = null) {
let data = {Grade: '0', term: '0', callbackName: 'jsonpCallback'}
if (Grade != null && term != null) {
data.Grade = Grade
data.term = term
}
return this.$jsonp(this.$store.state.app_host + this.$store.getters.urlPaths.u_schedule, data)
}
// 獲取當(dāng)前周
function getCurrentWeek() {
let data = {callbackName: 'jsonpCallback'}
return this.$jsonp(this.$store.state.app_host + this.$store.getters.urlPaths.u_week, data)
}
export {loginjs,getSelection,getScore,getScheduleJs,getCurrentWeek}
8.課表頁面實現(xiàn):這里使用了另外的swiper不是muse-ui的香罐,因為muse-ui的swiper實現(xiàn)不了想要的效果卧波。就在好友的建議下使用了另外的vue-awesome-swiper它是基于swiper4的。比較難受的就是庇茫,我一開始就打算用它的loop屬性港粱,但是添加上去的時候就出錯了,網(wǎng)上也很少相關(guān)的內(nèi)容港令,就先放棄了loop屬性啥容。就選擇直接生成21張的課表的swiper-slide,在朋友的iphone 瀏覽器打開居然卡了,沒辦法就必須優(yōu)化顷霹,然后在嘗試和看swiper4的文檔咪惠,后來發(fā)現(xiàn)可以先不讓swiper進行init,先讓它的params的loop的值初始為true后再調(diào)用init淋淀,結(jié)果成功了遥昧,然后swiper-slide的數(shù)量可以減到3了《浞祝【如果我不執(zhí)著與想要swiper的cude立體轉(zhuǎn)換效果炭臭,根本就不用這么麻煩,1個swiper-slide就可以袍辞,也不用用到loop】
代碼:Schedule.vue
9.最后是學(xué)習(xí)筆記鞋仍。