ModelViewSet+ModelSerializer+Vue实现图书管理系统

1 页面逻辑分析

1.1 父子组件传参思路

1.1.1 父传子
父组件中用:data传递要传的data
子组件中用props['data']来接收
(子组件中可以用v-model绑定,这样就能双向绑定父组件中的data值)
1.1.2 子传父
子组件中用this.$emit('look', 'data')
		# this.$emit('父组件中定义方法', '要传递的参数')

@look="look"
父组件中在方法中调用 look(data){
    
}

1.2 整体页面逻辑

1.2.1 展示
  • 前端展示出所有数据

1.2.2 修改
  • 点击修改时,在原有数据基础上进行修改(涉及得到父子组件传参)

1.2.3 删除
  • 因为对接的是ModelViewSet,所以执行的是物理删除(没有业务逻辑,所以没有写APIView)

1.2.4 添加
  • 在子组件上进行添加,实际上是v-model绑定的数据,数据将会写在父组件上

2 Django端

2.1 项目目录结构

book_project
├── book
│   ├── apps(源根目录)
│   │   ├── bookapp
│   │   │   ├── admin.py
│   │   │   ├── apps.py
│   │   │   ├── __init__.py
│   │   │   ├── migrations
│   │   │   │   ├── __init__.py
│   │   │   ├── models.py
│   │   │   ├── serializers.py
│   │   │   ├── tests.py
│   │   │   ├── urls.py
│   │   │   └── views.py
│   │   └── __init__.py
│   ├── book
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   ├── db.sqlite3
│   ├── libs
│   │   └── __init__.py
│   ├── manage.py
│   ├── static
│   ├── templates
│   └── utils
│       └── __init__.py
├── celery_task
│   └── __init__.py
├── logs
├── packages
├── README.en.md
├── README.md
├── scrips
└── uwsgi_conf

2.2 settings.py

import os, sys

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))

SECRET_KEY = '@77(0ge9)z*nv62wlk$*64o5nk&)s^9av9v6oqd&94br9ms-m!'

DEBUG = True

ALLOWED_HOSTS = ['*']


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'bookapp.apps.BookappConfig'
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    '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
ROOT_URLCONF = 'book.urls'
CORS_ORIGIN_WHITELIST = (
    'http://127.0.0.1:8080',
    'http://localhost:8080',
)

CORS_ALLOW_CREDENTIALS = True	# 允许携带cookie

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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',
            ],
        },
    },
]

WSGI_APPLICATION = 'book.wsgi.application'


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'book_db',
        'USER': 'root',
        'PASSWORD': '1',
        'HOST': '127.0.0.1',
        'PORT': '3306'
    }
}


AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False


STATI_URL = '/static/'

2.3 urls.py(主路由)

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('book/', include('bookapp.urls'))
]

2.4 urls.py(子路由)

from django.urls import path
from rest_framework.routers import DefaultRouter

from bookapp import views

router = DefaultRouter()
router.register('books', views.BooksViewSet)
urlpatterns = [

]
urlpatterns += router.urls

2.5 models.py

from django.db import models

class Books(models.Model):
    books_name = models.CharField(max_length=30)
    author = models.CharField(max_length=30)
    desc = models.TextField()
    datetime = models.DateField()

2.6 views.py

from rest_framework import viewsets

from bookapp.models import Books
from bookapp.serializers import BooksSerializer


class BooksViewSet(viewsets.ModelViewSet):
    queryset = Books.objects.all()
    serializer_class = BooksSerializer

3 Vue端

3.1 项目目录结构

book_vue
├─ build
├─ config
├─ node_modules
├─ src
│  ├─ assets
│  ├─ components
│  ├─ http
│  │  ├── apis.js
│  │  ├── index.js
│  ├─ router
│  │  ├── index.js
│  └─ views
│     ├── components
│  │  │  ├── ChildBook.vue
│  │  ├── FatherBook.vue  
│  ├── App.vue
│  ├── main.js 
├── static
├── .babelrc
├── .editorconfig
├── .postcssrc.js
├── index.html
├── package.json
└── README.md

3.2 安装 element-ui

cnpm i element-ui -S  # element—ui

3.3 main.js

import Vue from 'vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App'
import router from './router'

Vue.config.productionTip = false
Vue.use(ElementUI);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

3.4 http/index.js

/* eslint-disable */
// 第一步:实例化axios对象,简单封装
const axios = require('axios');                         // 生成一个axios实例
axios.defaults.baseURL = 'http://192.168.56.100:1594';  // 设置请求后端的URL地址
axios.defaults.timeout = 10000;                         // axios请求超时时间
axios.defaults.withCredentials = true;
axios.defaults.headers['Content-Type'] = 'application/json';        // axios发送数据时使用json格式
axios.defaults.transformRequest = data => JSON.stringify(data);     // 发送数据前进行json格式化


//  第二:设置拦截器
// 
//  请求拦截器(当前端发送请求给后端前进行拦截)
//  例1:请求拦截器获取token设置到axios请求头中,所有请求接口都具有这个功能
//  例2:到用户访问某一个页面,但是用户没有登录,前端页面自动跳转 /login/ 页面
// 
axios.interceptors.request.use(config => {
    // 从localStorage中获取token
    // let token = localStorage.getItem('token');
    // 如果有token, 就把token设置到请求头中Authorization字段中
    // token && (config.headers.Authorization = token);
    return config;
}, error => {
    return Promise.reject(error);
});

//  响应拦截器(当后端返回数据的时候进行拦截)
//  例1:当后端返回状态码是401/403时,跳转到 /login/ 页面
// 
axios.interceptors.response.use(response => {
    // 当响应码是 2xx 的情况, 进入这里
    // debugger
    return response.data;
}, error => {
    // 当响应码不是 2xx 的情况, 进入这里
    // debugger
    return error
});

//  第三:根据上面分装好的axios对象,封装 get、post、put、delete请求
// 
//  get方法,对应get请求
//  @param {String} url [请求的url地址]
//  @param {Object} params [请求时携带的参数]
// 
export function get(url, params, headers) {
    return new Promise((resolve, reject) => {
        axios.get(url, { params, headers }).then(res => {
            resolve(res)
        }).catch(err => {

            reject(err)
        })
    })
}

// 
//  post方法,对应post请求
//  @param {String} url [请求的url地址]
//  @param {Object} params [请求时携带的参数]
// 
export function post(url, params, headers) {
    return new Promise((resolve, reject) => {
        axios.post(url, params, headers).then((res) => {
            resolve(res)
        }).catch((err) => {
            reject(err)
        })
    })
}
export function put(url, params, headers) {
    return new Promise((resolve, reject) => {
        axios.put(url + params.id + '/', params.data, headers).then((res) => {
            // ModelViewSet 真的是个大坑,修改后面必须以 ‘/’ 结束
            resolve(res)
        }).catch((err) => {
            reject(err)
        })
    })
}
export function del(url, params, headers) {
    return new Promise((resolve, reject) => {
        axios.delete(url + params,  headers ).then((res) => {
            // 删除过于复杂,我直接修改成这样的了
            resolve(res)
        }).catch((err) => {
            reject(err)
        })
    })
}
export default axios;

3.5 http/apis.js

import { get, post, put, del } from './index'

export const getBookList = (params, headers) => get("/book/books/", params, headers)
export const addBook = (params, headers) => post("/book/books/", params, headers) 
export const updateBook = (params, headers) => put("/book/books/", params, headers) 
export const delBook = (params, headers) => del("/book/books/", params, headers)

3.6 router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import FatherBook from '@/views/FatherBook'
import ChildBook from '@/views/components/ChildBook'
// @ 代表 src

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    },
    {
      path: '/father_book',
      name: 'FatherBook',
      component: FatherBook
    },
    {
      path: '/child_book',
      name: 'ChildBook',
      component: ChildBook
    }
  ]
})

3.7 views/FatherBook.vue

<template>
    <div> 
        <h3>图书管理系统&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;<el-button @click="addNew">添加图书</el-button></h3>
        <Child 
            :visible.sync="dialogFormVisible"
            :editData='editData'
            @addBookList='addBookList'
        >
        <!-- :visible.sync="dialogFormVisible:控制子组件是否显示,是控制子组件中的属性!并非父组件是否显示子组件 -->
        <!-- :editData='editData':把 editData传递给子组件 -->
        <!-- @addBookList='addBookList':不调用是没办法触发子组件增加或者修改图书时的方法的 -->
        </Child>
        <el-table
            :data="book_list"
            style="width: 100%"
            
            
        >
            <el-table-column
                label="id"
                width="50"
                prop='id'>
            </el-table-column>

            <el-table-column
                label="图书名"
                width="180"
                prop='books_name'>
            </el-table-column>

            <el-table-column
                label="作者"
                width="80"
                prop='author'>
            </el-table-column>

            <el-table-column
                label="描述"
                width="380"
                prop='desc'>
            </el-table-column>

            <el-table-column
                label="日期"
                width="130"
                prop="datetime">
            </el-table-column>

            <el-table-column
                label="操作"  
            >
                <template slot-scope="scope"> 
                    <!-- 用于获取id,不得不写 -->
                    <el-button type="primary" icon="el-icon-edit" circle @click="updateBook(scope.row.id)"></el-button>
                    <el-button type="danger" icon="el-icon-delete" circle  @click="deleteBook(scope.row.id)"></el-button>    
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

<script>
import { getBookList, addBook, delBook, updateBook } from '@/http/apis'
import ChildBook from '@/views/components/ChildBook'
import Child from './components/ChildBook'
export default {
    components:{
        Child
    },
    data() {
        return {
            book_list:[

            ],
            dialogFormVisible:false,
            editData:{
                    'books_name':'',
                    'author':'',
                    'desc':'',
                    'datetime':''
            }
        }
    },
    methods: {
        bookList(){
            getBookList().then(res=>{
                this.book_list = res
            })
        },
        addNew(){
            this.dialogFormVisible = true
            // 初始化列表,否则下次修改或添加会包含上一次的数据
            this.editData = 
                {
                    'books_name':'',
                    'author':'',
                    'desc':'',
                    'datetime':''
                }
            
        },
        addBookList(){
            if(this.editData.id){
                let params = {
                    id: this.editData.id,
                    data: this.editData
                }
                updateBook(params).then(res=>{
                    console.log(res)
                    alert('修改成功')
                    this.bookList()
                })

            }else{
                addBook(this.editData).then(res=>{
                    console.log(res)
                    alert('添加图书成功')
                    this.bookList()
                })
            }

            this.dialogFormVisible = true
        },
        deleteBook(p){
            console.log(typeof id)
            // console.log(params)
            delBook(p).then(res=>{
                console.log(res)
                alert('删除成功')
                this.bookList()
            })

        },
        updateBook(book_id){
            
            this.editData = JSON.parse(JSON.stringify(this.book_list[book_id-1]))
            // 固定方法, 把父组件列表里的值复制到 editData中,再传给子组件显示出来
            this.dialogFormVisible = true 
            console.log(this.editData)
        }

    },
    created() {
        this.bookList()
    }
}
</script>

<style scoped>
.el-table .warning-row {
    background: oldlace;
}

.el-table .success-row {
    background: #f0f9eb;
}

</style>

3.8 views/components/ChildBook.vue

<template>
    <div>
        <el-dialog title="请进行操作" :visible="visible"> 
            <div>
                <span>图书名:</span> 
                <el-input class='elinput' style="width:400px" v-model="editData.books_name"></el-input> 
                
            </div>
            <br> 
            <div>
                <span>作&emsp;者:</span> 
                <el-input class='elinput' style="width:400px" v-model="editData.author"></el-input> 
                                                                        <!-- 与父组件中的 editData 中的值双向绑定 -->
            </div> 
            <br>
            <div>
                <span>描&emsp;述:</span> 
                <el-input class='elinput' style="width:400px" v-model="editData.desc"></el-input> 
            </div> 
            <br>
            <div>
                <span>日&emsp;期:</span> 
                <el-input class='elinput' style="width:400px" v-model="editData.datetime"></el-input> 
            </div> 
            <br>
            <el-button @click="cancel">取 消</el-button> 
            <el-button type="primary" @click="writeBook">确 定</el-button> 
        </el-dialog>
    </div>
</template>

<script>
export default {
    props:[ 'visible', 'editData' ],
    // 参数 visible 是控制弹窗是否显示的
    // 参数 editData 是父组件中传递过来的图书空列表,子组件中的值和它进行 v-model 双向绑定
    data() {
        return {
            dialogFormVisible: false,

        }
    },
    methods: {
        cancel(){
            this.$emit('update:visible', false)
            // 关闭弹窗
        },
        writeBook(){
            this.$emit('addBookList')
            // 调用父组件中 addBookList方法,调用的是最外面的方法哦,因为添加和修改要写在一起
            this.$emit('update:visible', false)
        }
    },
    created() {

    }
}
</script>
.el-input { 
    width: 120px; 
    height: 40px; 
}
<style scoped>

</style>
posted @ 2020-11-22 20:49  狐狸大大爱吃糖  阅读(287)  评论(0编辑  收藏  举报