前情提要
之前出于自身興趣饼问,給娃幼兒園寫了個(gè)每日菜單的網(wǎng)站,并添加到訂閱號(hào)中(V1版簡(jiǎn)書(shū)鏈接)揭斧。主要通過(guò)頁(yè)面上的js與Django后臺(tái)交互莱革,同樣通過(guò)頁(yè)面上的js完成返回?cái)?shù)據(jù)的解析和呈現(xiàn)。
為了更系統(tǒng)地完成前端開(kāi)發(fā)讹开,這次一邊學(xué)習(xí)vue.js框架盅视,一邊利用vue改寫前端,于是就有了菜單網(wǎng)站項(xiàng)目的第二版(github鏈接)旦万。
項(xiàng)目結(jié)構(gòu)
前端改用vue.js構(gòu)建闹击,后端仍然使用django服務(wù),處理前端的數(shù)據(jù)請(qǐng)求成艘,完成數(shù)據(jù)庫(kù)查詢赏半。
整個(gè)項(xiàng)目目錄結(jié)構(gòu)如下:
kinder_foods_vue_django
├─backend
│ ├─__pycache__
│ ├─__init__.py
│ ├─settings.py
│ ├─urls.py
│ └─wsgi.py
├─frontend
│ ├─dist
│ │ ├─static
│ │ │ ├─css
│ │ │ └─js
│ │ └─index.html
│ ├─node_modules
│ ├─public
│ ├─src
│ │ ├─assets
│ │ ├─components
│ │ │ ├─MenuTable.vue
│ │ │ └─SelectDate.vue
│ │ ├─views
│ │ │ ├─ChartView.vue
│ │ │ └─MenuView.vue
│ │ ├─App.vue
│ │ ├─main.js
│ │ └─router.js
│ ├─babel.config.js
│ ├─package.json
│ ├─README.md
│ └─vue.config.js
├─menu
└─manage.py
frontend目錄為vue的項(xiàng)目目錄贺归,其余為Django項(xiàng)目,Django的APP名為menu断箫。
準(zhǔn)備工作
檢查版本
vue -V
C:\Users\ZHANG JIZHONG\Documents\Projects\Python\kinder_foods_vue_django>vue -V
3.8.4
使用VUE UI創(chuàng)建項(xiàng)目
網(wǎng)上用命令行腳手架的教程較多拂酣,這里就偷懶用ui來(lái)創(chuàng)建項(xiàng)目了。
vue ui
C:\Users\ZHANG JIZHONG\Documents\Projects\Python\kinder_foods_vue_django>vue ui
?? Starting GUI...
?? Ready on http://localhost:8000
因?yàn)槎际莡i方式的仲义,每一步截圖會(huì)比較多踱葛,這里選取選擇功能的截圖作為代表,圖中開(kāi)啟了Bable光坝、Router和Linter功能尸诽。
添加bootstrap-vue引用
同樣地,依賴也可以通過(guò)ui方式安裝:
搜索并安裝bootstrap-vue盯另,并在全局main.js中添加以下內(nèi)容方能使用:
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
Vue前端結(jié)構(gòu)
組件
- MenuTable.vue
負(fù)責(zé)呈現(xiàn)菜單的組件性含,使用bootstrap-vue中的<b-table>,綁定計(jì)算屬性daily_menu
computed: {
daily_menu: function () {
for (let week in this.menu) {
return this.menu[week];
}
}
},
其中鸳惯,menu是MenuTable組件的屬性:
props: ['menu'],
template塊主要內(nèi)容:
<template>
<b-table responsive="md" bordered :items="daily_menu">
<template slot="日期" slot-scope="data">
<strong>{{ data.value.date }}<br/>{{ data.value.weekday }}</strong>
</template>
<span slot="早點(diǎn)" slot-scope="data" v-html="data.value"></span>
<span slot="午餐" slot-scope="data" v-html="data.value"></span>
<span slot="午點(diǎn)" slot-scope="data" v-html="data.value"></span>
<span slot="體弱兒營(yíng)養(yǎng)菜" slot-scope="data" v-html="data.value"></span>
</b-table>
</template>
可見(jiàn)該表格共5列商蕴,日期列分行顯示日期和星期幾,其余列直接提取同名屬性即可芝发,這些屬性都是原生html绪商。
至于為什么要用html,是因?yàn)槊款D餐食的品種數(shù)量是不確定的辅鲸,需要添加換行標(biāo)簽
格郁。
為了方便起見(jiàn),后端返回的數(shù)據(jù)也做了相應(yīng)修改(關(guān)于這點(diǎn)在Django的views部分中具體描述)独悴。
- SelectDate.vue
選擇日期的組件例书,依賴于charliekassel/vuejs-datepicker,在本地需先導(dǎo)入
import Datepicker from 'vuejs-datepicker';
import * as lang from "vuejs-datepicker/src/locale";
lang用于本地化刻炒,中文格式設(shè)置如下:
data() {
return {
format: "yyyy年MM月dd日",
language: "zh",
languages: lang,
}
},
詳細(xì)的配置可以訪問(wèn)作者的github
template塊的主要內(nèi)容:
<template>
<div>
<datepicker placeholder="選擇日期" :language="languages[language]" :format="format" v-on:selected = "sendEvent" >
</datepicker>
</div>
</template>
為該組件配置了一個(gè)事件决采,當(dāng)日期被選中時(shí),調(diào)用sendEvent方法坟奥,方法定義:
sendEvent: function(query_date) {
this.$emit("selected", query_date);
}
該方法將選中的日期作為參數(shù)树瞭,使當(dāng)前組件產(chǎn)生一個(gè)"selected"事件(這個(gè)selected與datepicker的selected同名,但不是同一個(gè)事件爱谁,是SelectDate組件的事件)
- MenuView.vue
一個(gè)容納以上兩個(gè)組件的視圖組件晒喷,內(nèi)容不多的情況下也可以省略這是視圖組件,直接把上面?zhèn)z組件放到App.vue里去管行。
template塊的主要內(nèi)容:
<template>
<div>
<SelectDate v-on:selected = "get_menu"/>
<b-alert v-model="failure" variant="danger" dismissible>
沒(méi)有{{query_date}}的數(shù)據(jù)哦!
</b-alert>
<MenuTable v-bind:menu = "menu"/>
</div>
</template>
監(jiān)聽(tīng)SelectDate組件的selected事件厨埋,捕捉后調(diào)用get_menu方法邪媳。
get_menu方法向后臺(tái)傳遞query_date屬性捐顷,請(qǐng)求menu數(shù)據(jù):
get_menu: function(query_date) {
let year = query_date.getFullYear();
let month = query_date.getMonth() + 1;
let day = query_date.getDate();
this.query_date = year + '年' + month + '月' + day + '日';
query_date = year + '-' + month + '-' + day;
this.$http.post('/ajax/',
{query_date: query_date},
{emulateJSON: true}) // 使用form-data才可以荡陷,否則querydict空
.catch(function(error) { // 處理未得到數(shù)據(jù)的異常
this.failure = true;
return Promise.reject(error);
}).then(function(response){ // 成功返回?cái)?shù)據(jù)
this.failure = false;
this.menu = response.data;
});
}
如果從/ajax/請(qǐng)求中成功獲取后臺(tái)數(shù)據(jù),那么更新this.menu屬性(綁定到了MenuTable的menu屬性)迅涮,達(dá)到更新MenuTable組件中menu這個(gè)prop的目的废赞。
如果未能成功獲取后臺(tái)數(shù)據(jù),那么this.failure為true叮姑,頁(yè)面顯示<b-alert>唉地,提示用戶沒(méi)有這一天的數(shù)據(jù)。
- ChartView.vue
這個(gè)組件原本打算進(jìn)行一些可視化的嘗試传透,目前還是空的耘沼,不過(guò)大致想好了放些什么了。既然是跟吃的有關(guān)的網(wǎng)站朱盐,那就放些食物營(yíng)養(yǎng)成分之類的圖表群嗤,類似于foodwake,不知道這方面有沒(méi)有更好的推薦兵琳?
- App.vue
這里就相對(duì)簡(jiǎn)單了狂秘,添加兩個(gè)路由鏈接和路由視圖即可。
template塊的主要內(nèi)容:
<template>
<div id="app">
<div id="nav">
<router-link to="/" class="text-white">菜單</router-link>
<router-link to="/chart" class="text-white">敬請(qǐng)期待</router-link>
</div>
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
為了使兩個(gè)視圖在切換的時(shí)候不銷毀躯肌,需要在<router-view>標(biāo)簽外添加<keep-alive>者春,這樣選擇的日期和呈現(xiàn)的內(nèi)容不會(huì)因?yàn)榍袚Q視圖而重新初始化。
- 修改頁(yè)面標(biāo)題
頁(yè)面的標(biāo)題即網(wǎng)頁(yè)標(biāo)簽上顯示的內(nèi)容清女,在HTML中存在于<head>標(biāo)簽的<title>中钱烟。
在vue.js中需要在路由中添加相關(guān)信息,router.js
routes: [
{
path: '/',
name: 'menu-view',
component: MenuView,
meta: {
title: '幼兒園每周菜譜'
}
},
]
其中meta.title就是添加的標(biāo)題信息嫡丙。還要在main.js中增加相應(yīng)處理:
router.beforeEach((to, from, next) => {
document.title = to.meta.title
next()
})
- 指定靜態(tài)文件目錄
npm打包時(shí)忠售,會(huì)將用到的css和js打包到靜態(tài)目錄中去。默認(rèn)的靜態(tài)目錄未必滿足要求迄沫,可以通過(guò)vue.config.js文件指定:
module.exports = {
assetsDir : 'static'
}
這里的assetsDir是基于outputDir的相對(duì)路徑稻扬,所以實(shí)際上是frontend/dist/static。
Django后端
前端全部交給了vue.js羊瘩,因此Django只用跑WEB服務(wù)泰佳,以及處理前端的請(qǐng)求。
views的修改
在MenuTable.vue組件中提到了尘吗,由于每頓餐食的數(shù)量不固定逝她,比如午餐可能是四鮮餛飩和瑪瑙松仁甜飯共兩樣食物,也可能是土豆肋排木耳湯睬捶、花菜炒雙菇黔宛、金玉滿堂和麥片飯共四種。糾結(jié)了一下擒贸,還是用python更為方便臀晃,在食物和食物之間直接拼接了
觉渴。這樣前端直接用原生HTML顯示即可。但是這個(gè)做法徽惋,就在前后端增加了耦合性案淋,而前后端分開(kāi)開(kāi)發(fā)的一個(gè)重要目的就是解耦,所以這方面有點(diǎn)小遺憾险绘。
整合前后端
這里采取的方法是由npm完成打包踢京,將打包后的目錄作為Django的“模板”目錄。
在settings.py中指定模板目錄(frontend/dist)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['frontend/dist'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
將前端的靜態(tài)目錄添加到Django的settings中宦棺,以便找到npm打包后的靜態(tài)文件
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "frontend/dist/static"),
]
url中添加對(duì)根路徑的訪問(wèn)瓣距,指向npm生成的index.html
url(r'^$', TemplateView.as_view(template_name='index.html'))
如此整合后,只需運(yùn)行Django服務(wù)代咸,即可完成整個(gè)網(wǎng)站的部署旨涝。
效果預(yù)覽
-
菜單頁(yè):
-
敬請(qǐng)期待頁(yè):
小結(jié)
這次主要是學(xué)習(xí)vue.js寫前端的過(guò)程,碰到過(guò)一些諸如頁(yè)面標(biāo)題侣背、靜態(tài)文件輸出路徑等小問(wèn)題白华,都能在網(wǎng)上找到解決方案,總體來(lái)說(shuō)還算比較順利贩耐。
項(xiàng)目主要還是以Django服務(wù)運(yùn)行弧腥,只是用vue代替了原本Django中的templates那一部分。因?yàn)镈jango主要是通過(guò)渲染模板完成前端呈現(xiàn)潮太,對(duì)于前端的開(kāi)發(fā)支持其實(shí)并不充分管搪。而用vue寫前端如果熟練的話效率會(huì)更高。??