前情提要
之前出于自身興趣,給娃幼兒園寫了個每日菜單的網(wǎng)站,并添加到訂閱號中(V1版簡書鏈接)。主要通過頁面上的js與Django后臺交互,同樣通過頁面上的js完成返回數(shù)據(jù)的解析和呈現(xiàn)。
為了更系統(tǒng)地完成前端開發(fā),這次一邊學(xué)習(xí)vue.js框架,一邊利用vue改寫前端,于是就有了菜單網(wǎng)站項目的第二版(github鏈接)。
項目結(jié)構(gòu)
前端改用vue.js構(gòu)建,后端仍然使用django服務(wù),處理前端的數(shù)據(jù)請求,完成數(shù)據(jù)庫查詢。
整個項目目錄結(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的項目目錄,其余為Django項目,Django的APP名為menu。
準備工作
檢查版本
vue -V
C:\Users\ZHANG JIZHONG\Documents\Projects\Python\kinder_foods_vue_django>vue -V
3.8.4
使用VUE UI創(chuàng)建項目
網(wǎng)上用命令行腳手架的教程較多,這里就偷懶用ui來創(chuàng)建項目了。
vue ui
C:\Users\ZHANG JIZHONG\Documents\Projects\Python\kinder_foods_vue_django>vue ui
?? Starting GUI...
?? Ready on http://localhost:8000

因為都是ui方式的,每一步截圖會比較多,這里選取選擇功能的截圖作為代表,圖中開啟了Bable、Router和Linter功能。

添加bootstrap-vue引用
同樣地,依賴也可以通過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
負責(zé)呈現(xiàn)菜單的組件,使用bootstrap-vue中的<b-table>,綁定計算屬性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="早點" slot-scope="data" v-html="data.value"></span>
<span slot="午餐" slot-scope="data" v-html="data.value"></span>
<span slot="午點" slot-scope="data" v-html="data.value"></span>
<span slot="體弱兒營養(yǎng)菜" slot-scope="data" v-html="data.value"></span>
</b-table>
</template>
可見該表格共5列,日期列分行顯示日期和星期幾,其余列直接提取同名屬性即可,這些屬性都是原生html。
至于為什么要用html,是因為每頓餐食的品種數(shù)量是不確定的,需要添加換行標簽
。
為了方便起見,后端返回的數(shù)據(jù)也做了相應(yīng)修改(關(guā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,
}
},
詳細的配置可以訪問作者的github
template塊的主要內(nèi)容:
<template>
<div>
<datepicker placeholder="選擇日期" :language="languages[language]" :format="format" v-on:selected = "sendEvent" >
</datepicker>
</div>
</template>
為該組件配置了一個事件,當日期被選中時,調(diào)用sendEvent方法,方法定義:
sendEvent: function(query_date) {
this.$emit("selected", query_date);
}
該方法將選中的日期作為參數(shù),使當前組件產(chǎn)生一個"selected"事件(這個selected與datepicker的selected同名,但不是同一個事件,是SelectDate組件的事件)
- MenuView.vue
一個容納以上兩個組件的視圖組件,內(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>
沒有{{query_date}}的數(shù)據(jù)哦!
</b-alert>
<MenuTable v-bind:menu = "menu"/>
</div>
</template>
監(jiān)聽SelectDate組件的selected事件,捕捉后調(diào)用get_menu方法。
get_menu方法向后臺傳遞query_date屬性,請求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){ // 成功返回數(shù)據(jù)
this.failure = false;
this.menu = response.data;
});
}
如果從/ajax/請求中成功獲取后臺數(shù)據(jù),那么更新this.menu屬性(綁定到了MenuTable的menu屬性),達到更新MenuTable組件中menu這個prop的目的。
如果未能成功獲取后臺數(shù)據(jù),那么this.failure為true,頁面顯示<b-alert>,提示用戶沒有這一天的數(shù)據(jù)。
- ChartView.vue
這個組件原本打算進行一些可視化的嘗試,目前還是空的,不過大致想好了放些什么了。既然是跟吃的有關(guān)的網(wǎng)站,那就放些食物營養(yǎng)成分之類的圖表,類似于foodwake,不知道這方面有沒有更好的推薦?
- App.vue
這里就相對簡單了,添加兩個路由鏈接和路由視圖即可。
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">敬請期待</router-link>
</div>
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
為了使兩個視圖在切換的時候不銷毀,需要在<router-view>標簽外添加<keep-alive>,這樣選擇的日期和呈現(xiàn)的內(nèi)容不會因為切換視圖而重新初始化。
- 修改頁面標題
頁面的標題即網(wǎng)頁標簽上顯示的內(nèi)容,在HTML中存在于<head>標簽的<title>中。
在vue.js中需要在路由中添加相關(guān)信息,router.js
routes: [
{
path: '/',
name: 'menu-view',
component: MenuView,
meta: {
title: '幼兒園每周菜譜'
}
},
]
其中meta.title就是添加的標題信息。還要在main.js中增加相應(yīng)處理:
router.beforeEach((to, from, next) => {
document.title = to.meta.title
next()
})
- 指定靜態(tài)文件目錄
npm打包時,會將用到的css和js打包到靜態(tài)目錄中去。默認的靜態(tài)目錄未必滿足要求,可以通過vue.config.js文件指定:
module.exports = {
assetsDir : 'static'
}
這里的assetsDir是基于outputDir的相對路徑,所以實際上是frontend/dist/static。
Django后端
前端全部交給了vue.js,因此Django只用跑WEB服務(wù),以及處理前端的請求。
views的修改
在MenuTable.vue組件中提到了,由于每頓餐食的數(shù)量不固定,比如午餐可能是四鮮餛飩和瑪瑙松仁甜飯共兩樣食物,也可能是土豆肋排木耳湯、花菜炒雙菇、金玉滿堂和麥片飯共四種。糾結(jié)了一下,還是用python更為方便,在食物和食物之間直接拼接了
。這樣前端直接用原生HTML顯示即可。但是這個做法,就在前后端增加了耦合性,而前后端分開開發(fā)的一個重要目的就是解耦,所以這方面有點小遺憾。
整合前后端
這里采取的方法是由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中添加對根路徑的訪問,指向npm生成的index.html
url(r'^$', TemplateView.as_view(template_name='index.html'))
如此整合后,只需運行Django服務(wù),即可完成整個網(wǎng)站的部署。
效果預(yù)覽
-
菜單頁:
菜單 -
敬請期待頁:
敬請期待倒計時
小結(jié)
這次主要是學(xué)習(xí)vue.js寫前端的過程,碰到過一些諸如頁面標題、靜態(tài)文件輸出路徑等小問題,都能在網(wǎng)上找到解決方案,總體來說還算比較順利。
項目主要還是以Django服務(wù)運行,只是用vue代替了原本Django中的templates那一部分。因為Django主要是通過渲染模板完成前端呈現(xiàn),對于前端的開發(fā)支持其實并不充分。而用vue寫前端如果熟練的話效率會更高。??

