一、背景
Django 作為后端框架,提供 api 接口,Vue.js 作為前端框架,代替 Django 薄弱的模板引擎,使得前后端完全分離,也適合單頁應(yīng)用的開發(fā)構(gòu)建。
本項(xiàng)目為一個(gè)單頁項(xiàng)目,實(shí)現(xiàn)功能:
- 運(yùn)行
Django項(xiàng)目,可查詢所有書籍 - 可添加書籍,并實(shí)時(shí)刷新
項(xiàng)目整體目錄結(jié)構(gòu)
├── django_vue/
├── approot/ # django app
├── django_vue # 項(xiàng)目主配置
| ├── settings.py
├── frontend/ # 前端文件
| ├── build/
| ├── config/
| ├── dist/ # 打包后前端文件放的位置
| ├── node_modules/ # 依賴、庫
| ├── src/
| ├── static/ # 純靜態(tài)資源
├── db.sqlite3
├── manage.py
二、環(huán)境準(zhǔn)備
- 后端:
-
Django 2.1.1:可用DRF Python3.6.9-
django-cors-headers:解決跨域問題
-
- 前端:
Vue.js-
Element-UI:餓了么推出的前臺(tái)UI框架,有許多精美的UI文件 -
vue-resource:提供ajax請(qǐng)求服務(wù) -
vue-cli腳手架:快速搭建Vue項(xiàng)目
三、構(gòu)建 Django 項(xiàng)目
1、創(chuàng)建項(xiàng)目和 app:
python manage.py startproject django_vue
python manage.py startapp approot
2、新增模型類 approot/models.py:
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=64, verbose_name='書名')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加時(shí)間')
def __str__(self):
return self.name
遷移數(shù)據(jù)庫文件:
python manage.py makemigrations
python manage.py migrate
3、配置路由:
項(xiàng)目根目錄,django_vue/urls.py:
from django.contrib import admin
from django.urls import path, include, re_path
from django.views.generic.base import TemplateView
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('approot.urls')), # 提供 api 接口,連接 app:approot
# Vue 前端頁面,后面會(huì)用到
re_path(r'^$', TemplateView.as_view(template_name='index.html')),
]
新增 approot/urls.py:
from django.urls import path
from approot import views
urlpatterns = [
path('book/list/', views.BookListView.as_view(), name='book_list'),
path('book/create/', views.BookCreateView.as_view(), name='book_create'),
]
新增兩個(gè) api 接口(路由),分別用于查詢所有書籍和添加書籍。
4、視圖函數(shù) approot/views.py:
import json
from django.core import serializers
from django.http import JsonResponse
from django.views.generic.base import View
from approot.models import Book
class BookListView(View):
"""書籍列表"""
def get(self, request):
res = {'code': 0, 'msg': '查詢成功', 'data': []}
try:
book_list = Book.objects.all()
book_list = json.loads(serializers.serialize("json", book_list))
res['data'] = book_list
except Exception as e:
res['code'] = -1
res['msg'] = '查詢失敗'
return JsonResponse(res)
class BookCreateView(View):
"""添加書籍"""
def get(self, request):
res = {'code': 0, 'msg': '添加成功', 'data': []}
try:
name = request.GET.get('name')
Book.objects.create(name=name)
except Exception as e:
res['code'] = -1
res['msg'] = '添加失敗'
return JsonResponse(res)
這里采用的是 Django CBV,當(dāng)然也可以使用 DRF。
5、配置文件:django_vue/settings.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'approot',
'corsheaders', # 跨域訪問設(shè)置
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # 跨域訪問設(shè)置
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ORIGIN_ALLOW_ALL = True # 新增的跨域訪問設(shè)置
# 模板文件
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 'DIRS': [os.path.join(BASE_DIR, 'templates')]
'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',
],
},
},
]
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
STATIC_URL = '/static/'
# 靜態(tài)文件
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "frontend/dist/static"),
]
這里需要提前安裝 pip install django-cors-headers,用于解決 Vue 模板向 Django 后端請(qǐng)求跨域問題。
至此后端基本已配置完畢,可用 postman 測試下接口是否能夠正常返回。
注意:模板文件和靜態(tài)文件中的
dist目錄,會(huì)在Vue打包時(shí)生成
四、構(gòu)建 Vue.js 前端項(xiàng)目
前端總共可以分為以下幾個(gè)步驟:
安裝
node.js、nrm,更換源為淘寶源安裝
vue-cli,并 使用vue-cli腳手架快速生成Vue.js模板文件安裝
Element-UI、vue-resource等依賴運(yùn)行測試:
npm run dev打包:
npm run build
安裝 node.js
node.js 安裝沒什么好說的,一路安裝即可,記得要勾選 Add PATH,添加到環(huán)境變量。
安裝成功后,測試:
C:\Users\hj>node --version
v12.18.0
C:\Users\hj>npm --version
6.14.4
nrm 安裝
nrm 用于提供一些常用 NPM 包鏡像地址,包括 cnpm、淘寶鏡像等
npm i nrm -g # 全局安裝
nrm ls # 查看當(dāng)前可用鏡像源地址
nrm use taobao # 切換鏡像源為 淘寶,速度會(huì)快很多
C:\Users\hj>nrm ls
npm -------- https://registry.npmjs.org/
yarn ------- https://registry.yarnpkg.com/
cnpm ------- http://r.cnpmjs.org/
* taobao ----- https://registry.npm.taobao.org/
nj --------- https://registry.nodejitsu.com/
npmMirror -- https://skimdb.npmjs.com/registry/
edunpm ----- http://registry.enpmjs.org/
vue-cli 快速生成項(xiàng)目
1、安裝
npm install -g vue-cli // 全局安裝
2、創(chuàng)建工程項(xiàng)目
cd django_vue // 切換到后端項(xiàng)目根目錄
vue init webpack frontend // 初始化項(xiàng)目,需要手動(dòng)配置一系列配置,如:項(xiàng)目描述、作者、打包方式,是否使用 ESLint 規(guī)范代碼等
目錄結(jié)構(gòu)
├── frontend/
├── build/ # webpack 編譯任務(wù)配置文明:開發(fā)環(huán)境與生產(chǎn)環(huán)境
├── config
| ├── index.js # 項(xiàng)目核心配置
├── src/
| ├── main.js # 程序入口文件
| ├── App.vue # 程序入口 vue 組件
| ├── components/ # 組件
| ├── assets/ # 資源文件夾,一般放圖片之類的
| ├── router/ # 路由
├── static/ # 純靜態(tài)資源
├── index.html
└── node_modules/ # 項(xiàng)目中的依賴模塊
└── .babelrc # bael 配置文件
└── .editorconfig # 編輯配置文件
└── .gitignore # 忽略文件
└── package.json # 項(xiàng)目文件,記載一些命令和依賴還有簡要項(xiàng)目描述信息
└── README.md
└── index.html # 入口模板文件
安裝其他依賴
Element-UI
提供 UI 組件
npm install element-ui --save
npm install element-theme -g # 全局安裝
引入 frontent/src/main.js:
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUi)
vue-resource
用于向后端發(fā)起請(qǐng)求
npm i vue-resource --save
引入 frontent/src/main.js:
import VueResource from 'vue-resource'
Vue.use(VueResource)
新增組件 Home
在 frontend/src/components/ 下添加新的組件 Home.vue:
<template>
<div class="home">
<el-row display="margin-top: 10px">
<el-input v-model="input" placeholder="請(qǐng)輸入書名" style="display:inline-table; width: 30%; float:left"></el-input>
<el-button type="primary" @click="addBook()" style="float:left; margin: 2px;">新增</el-button>
</el-row>
<el-row>
<el-table :data="bookList" style="width: 100%" border>
<el-table-column prop="id" label="編號(hào)" min-width="100">
<template scope="scope"> {{ scope.row.pk }}</template>
</el-table-column>
<el-table-column prop="book_name" label="書名" min-width="100">
<template scope="scope"> {{ scope.row.fields.name }}</template>
</el-table-column>
<el-table-column prop="add_time" label="添加時(shí)間" min-width="100">
<template scope="scope"> {{ scope.row.fields.add_time }}</template>
</el-table-column>
</el-table>
</el-row>
</div>
</template>
<script>
export default {
name: "home",
data () {
return {
input: '',
bookList: [],
}
},
mounted: function(){
this.showBooks()
},
methods: {
addBook() {
this.$http.get('http://127.0.0.1:8000/api/book/create?name=' + this.input)
.then((response) => {
var res = JSON.parse(response.bodyText);
if (res.code === 0) {
this.showBooks()
} else {
this.$message.error('新增書籍失敗,請(qǐng)重試');
console.log(res['msg']);
}
})
},
showBooks() {
this.$http.get('http://127.0.0.1:8000/api/book/list')
.then((response) => {
var res = JSON.parse(response.bodyText);
console.log('查詢書籍:', res);
if (res.code === 0) {
this.bookList = res['data']
} else {
this.$message.error("查詢書籍失??!");
console.log(res['msg']);
}
})
}
}
}
</script>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
其中 el-row 為 Element-UI 中所有,$.http 為 vue-resource 所有。該組件作用:
-
template:展示一個(gè)表格(書籍信息) -
script:向后端發(fā)送請(qǐng)求,請(qǐng)求數(shù)據(jù)(查詢、新增書籍)
注意:這里使用到
Element-UI、vue-resource,還需要在frontend/src/router/index.js進(jìn)行引用
配置前端路由
frontend/src/router/index.js :
import Vue from 'vue'
import Router from 'vue-router'
// import HelloWorld from '@/components/HelloWorld'
import Home from '@/components/Home' // 一定要先引用 Home 組件,否則運(yùn)行失敗
import ElementUi from 'element-ui'
// import '@/theme-et/index.css'
Vue.use(ElementUi)
Vue.use(Router)
export default new Router({
// routes: [
// {
// path: '/',
// name: 'HelloWorld',
// component: HelloWorld
// }
// ]
routes: [
{
path: '/',
name: 'Home', // Home 組件名稱
component: Home
}
]
})
打包測試
在此之前我們已經(jīng)用 postman 測試了后端 api 接口,這里將測試前端能夠正常展示頁面。
1、測試運(yùn)行前端項(xiàng)目
cd frontend
npm run dev
訪問:http://localhost:8080/,F12 診斷查看下是否報(bào)錯(cuò)。
2、打包
cnpm install // 安裝依賴,或 npm install
cd frontend
npm run build // 打包,會(huì)在 frontend/ 下生成 dist 目錄,其中有static/、index.html
打包成功的信息:

關(guān)閉前端 npm run dev,運(yùn)行后端 python manage.py runserver,訪問 http://127.0.0.1:8000/#/ 即可,若一切正常的話可以看到如下界面:

項(xiàng)目源碼:https://github.com/hj1933/django_vue