娃在幼兒園吃了些啥V2:用Vue.js + Django重寫項目

前情提要

之前出于自身興趣,給娃幼兒園寫了個每日菜單的網(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

Vue項目管理器

因為都是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)

組件

  1. 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部分中具體描述)。

  1. 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組件的事件)

  1. 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ù)。

  1. ChartView.vue

這個組件原本打算進行一些可視化的嘗試,目前還是空的,不過大致想好了放些什么了。既然是跟吃的有關(guān)的網(wǎng)站,那就放些食物營養(yǎng)成分之類的圖表,類似于foodwake,不知道這方面有沒有更好的推薦?

  1. 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)容不會因為切換視圖而重新初始化。

  1. 修改頁面標題

頁面的標題即網(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()
})
  1. 指定靜態(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寫前端如果熟練的話效率會更高。??

參考

整合 Django + Vue.js 框架快速搭建web項目

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容