前情提要
之前出于自身兴趣,给娃幼儿园写了个每日菜单的网站,并添加到订阅号中(V1版链接)。主要通过页面上的js与Django后台交互,同样通过页面上的js完成返回数据的解析和呈现。
为了更系统地完成前端开发,这次一边学习vue.js框架,一边利用vue改写前端,于是就有了菜单网站项目的第二版(github链接)。
项目结构
前端改用vue.js构建,后端仍然使用django服务,处理前端的数据请求,完成数据库查询。
整个项目目录结构如下:
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创建项目
网上用命令行脚手架的教程较多,这里就偷懒用ui来创建项目了。
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中添加以下内容方能使用:
import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
Vue前端结构
组件
- MenuTable.vue
负责呈现菜单的组件,使用bootstrap-vue中的
computed: {
daily_menu: function () {
for (let week in this.menu) {
return this.menu[week];
}
}
},
其中,menu是MenuTable组件的属性:
props: ['menu'],
template块主要内容:
{{ data.value.date }}
{{ data.value.weekday }}
可见该表格共5列,日期列分行显示日期和星期几,其余列直接提取同名属性即可,这些属性都是原生html。
至于为什么要用html,是因为每顿餐食的品种数量是不确定的,需要添加换行标签
。
为了方便起见,后端返回的数据也做了相应修改(关于这点在Django的views部分中具体描述)。
- SelectDate.vue
选择日期的组件,依赖于charliekassel/vuejs-datepicker,在本地需先导入
import Datepicker from 'vuejs-datepicker';
import * as lang from "vuejs-datepicker/src/locale";
lang用于本地化,中文格式设置如下:
data() {
return {
format: "yyyy年MM月dd日",
language: "zh",
languages: lang,
}
},
详细的配置可以访问作者的github
template块的主要内容:
为该组件配置了一个事件,当日期被选中时,调用sendEvent方法,方法定义:
sendEvent: function(query_date) {
this.$emit("selected", query_date);
}
该方法将选中的日期作为参数,使当前组件产生一个"selected"事件(这个selected与datepicker的selected同名,但不是同一个事件,是SelectDate组件的事件)
- MenuView.vue
一个容纳以上两个组件的视图组件,内容不多的情况下也可以省略这是视图组件,直接把上面俩组件放到App.vue里去。
template块的主要内容:
没有{{query_date}}的数据哦!
监听SelectDate组件的selected事件,捕捉后调用get_menu方法。
get_menu方法向后台传递query_date属性,请求menu数据:
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) { // 处理未得到数据的异常
this.failure = true;
return Promise.reject(error);
}).then(function(response){ // 成功返回数据
this.failure = false;
this.menu = response.data;
});
}
如果从/ajax/请求中成功获取后台数据,那么更新this.menu属性(绑定到了MenuTable的menu属性),达到更新MenuTable组件中menu这个prop的目的。
如果未能成功获取后台数据,那么this.failure为true,页面显示
- ChartView.vue
这个组件原本打算进行一些可视化的尝试,目前还是空的,不过大致想好了放些什么了。既然是跟吃的有关的网站,那就放些食物营养成分之类的图表,类似于foodwake,不知道这方面有没有更好的推荐?
- App.vue
这里就相对简单了,添加两个路由链接和路由视图即可。
template块的主要内容:
为了使两个视图在切换的时候不销毁,需要在
- 修改页面标题
页面的标题即网页标签上显示的内容,在HTML中存在于
标签的在vue.js中需要在路由中添加相关信息,router.js
routes: [
{
path: '/',
name: 'menu-view',
component: MenuView,
meta: {
title: '幼儿园每周菜谱'
}
},
]
其中meta.title就是添加的标题信息。还要在main.js中增加相应处理:
router.beforeEach((to, from, next) => {
document.title = to.meta.title
next()
})
- 指定静态文件目录
npm打包时,会将用到的css和js打包到静态目录中去。默认的静态目录未必满足要求,可以通过vue.config.js文件指定:
module.exports = {
assetsDir : 'static'
}
这里的assetsDir是基于outputDir的相对路径,所以实际上是frontend/dist/static。
Django后端
前端全部交给了vue.js,因此Django只用跑WEB服务,以及处理前端的请求。
views的修改
在MenuTable.vue组件中提到了,由于每顿餐食的数量不固定,比如午餐可能是四鲜馄饨和玛瑙松仁甜饭共两样食物,也可能是土豆肋排木耳汤、花菜炒双菇、金玉满堂和麦片饭共四种。纠结了一下,还是用python更为方便,在食物和食物之间直接拼接了
。这样前端直接用原生HTML显示即可。但是这个做法,就在前后端增加了耦合性,而前后端分开开发的一个重要目的就是解耦,所以这方面有点小遗憾。
整合前后端
这里采取的方法是由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',
],
},
},
]
将前端的静态目录添加到Django的settings中,以便找到npm打包后的静态文件
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "frontend/dist/static"),
]
url中添加对根路径的访问,指向npm生成的index.html
url(r'^$', TemplateView.as_view(template_name='index.html'))
如此整合后,只需运行Django服务,即可完成整个网站的部署。
效果预览
-
菜单页:
-
敬请期待页:
小结
这次主要是学习vue.js写前端的过程,碰到过一些诸如页面标题、静态文件输出路径等小问题,都能在网上找到解决方案,总体来说还算比较顺利。
项目主要还是以Django服务运行,只是用vue代替了原本Django中的templates那一部分。因为Django主要是通过渲染模板完成前端呈现,对于前端的开发支持其实并不充分。而用vue写前端如果熟练的话效率会更高。
参考
整合 Django + Vue.js 框架快速搭建web项目