Django + Vue 实现前后端分离单页项目

一、背景

Django 作为后端框架,提供 api 接口,Vue.js 作为前端框架,代替 Django 薄弱的模板引擎,使得前后端完全分离,也适合单页应用的开发构建。

本项目为一个单页项目,实现功能:

  • 运行 Django 项目,可查询所有书籍
  • 可添加书籍,并实时刷新

项目整体目录结构

├── django_vue/ 
    ├── approot/        # django app
    ├── django_vue      # 项目主配置
    |    ├── settings.py    
    ├── frontend/       # 前端文件
    |    ├── build/ 
    |    ├── config/    
    |    ├── dist/  # 打包后前端文件放的位置
    |    ├── node_modules/  # 依赖、库
    |    ├── src/   
    |    ├── static/    # 纯静态资源
    ├── db.sqlite3      
    ├── manage.py

二、环境准备

  • 后端:
    • Django 2.1.1:可用 DRF
    • Python3.6.9
    • django-cors-headers:解决跨域问题
  • 前端:
    • Vue.js
    • Element-UI:饿了么推出的前台 UI 框架,有许多精美的 UI 文件
    • vue-resource:提供 ajax 请求服务
    • vue-cli 脚手架:快速搭建 Vue 项目

三、构建 Django 项目

1、创建项目和 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='添加时间')

    def __str__(self):
        return self.name

迁移数据库文件:

python manage.py makemigrations
python manage.py migrate

3、配置路由:

项目根目录,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 前端页面,后面会用到
    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'),
]

新增两个 api 接口(路由),分别用于查询所有书籍和添加书籍。

4、视图函数 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,当然也可以使用 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',  # 跨域访问设置
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',  # 跨域访问设置
    '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  # 新增的跨域访问设置

# 模板文件
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/'

# 静态文件
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "frontend/dist/static"),
]

这里需要提前安装 pip install django-cors-headers,用于解决 Vue 模板向 Django 后端请求跨域问题。

至此后端基本已配置完毕,可用 postman 测试下接口是否能够正常返回。

注意:模板文件和静态文件中的 dist 目录,会在 Vue 打包时生成

四、构建 Vue.js 前端项目

前端总共可以分为以下几个步骤:

  • 安装 node.js、nrm,更换源为淘宝源

  • 安装 vue-cli,并 使用 vue-cli 脚手架快速生成 Vue.js 模板文件

  • 安装 Element-UI、vue-resource 等依赖

  • 运行测试:npm run dev

  • 打包:npm run build

安装 node.js

node.js 安装没什么好说的,一路安装即可,记得要勾选 Add PATH,添加到环境变量。

官网地址

安装成功后,测试:

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  # 查看当前可用镜像源地址
nrm use taobao  # 切换镜像源为 淘宝,速度会快很多

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 快速生成项目

1、安装

npm install -g vue-cli      // 全局安装

2、创建工程项目

cd django_vue   // 切换到后端项目根目录
vue init webpack frontend   // 初始化项目,需要手动配置一系列配置,如:项目描述、作者、打包方式,是否使用 ESLint 规范代码等

目录结构

├── frontend/   
    ├── build/      # webpack 编译任务配置文明:开发环境与生产环境
    ├── config
    |    ├── index.js   # 项目核心配置
    ├── src/
    |    ├── main.js    # 程序入口文件
    |    ├── App.vue    # 程序入口 vue 组件
    |    ├── components/    # 组件
    |    ├── assets/    # 资源文件夹,一般放图片之类的
    |    ├── router/    # 路由
    ├── static/     # 纯静态资源
    ├── index.html
    └── node_modules/   # 项目中的依赖模块
    └── .babelrc    # bael 配置文件
    └── .editorconfig   # 编辑配置文件
    └── .gitignore  # 忽略文件
    └── package.json    # 项目文件,记载一些命令和依赖还有简要项目描述信息
    └── 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

用于向后端发起请求

 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="请输入书名" 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="编号" 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="添加时间" 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('新增书籍失败,请重试');
              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-rowElement-UI 中所有,$.httpvue-resource 所有。该组件作用:

  • template:展示一个表格(书籍信息)
  • script:向后端发送请求,请求数据(查询、新增书籍)

注意:这里使用到 Element-UI、vue-resource,还需要在 frontend/src/router/index.js 进行引用

配置前端路由

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 组件,否则运行失败
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
    }
  ]
})

打包测试

在此之前我们已经用 postman 测试了后端 api 接口,这里将测试前端能够正常展示页面。

1、测试运行前端项目

cd frontend
npm run dev

访问:http://localhost:8080/F12 诊断查看下是否报错。

2、打包

cnpm install  // 安装依赖,或 npm install

cd frontend
npm run build   // 打包,会在 frontend/ 下生成 dist 目录,其中有static/、index.html

打包成功的信息:

image

关闭前端 npm run dev,运行后端 python manage.py runserver,访问 http://127.0.0.1:8000/#/ 即可,若一切正常的话可以看到如下界面:

image

项目源码:https://github.com/hj1933/django_vue

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345