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>图书管理系统      <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=" 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="400px" v-model="editData.books_name"></el-input>
</div>
<br>
<div>
<span>作 者:</span>
<el-input class='elinput' style="400px" v-model="editData.author"></el-input>
<!-- 与父组件中的 editData 中的值双向绑定 -->
</div>
<br>
<div>
<span>描 述:</span>
<el-input class='elinput' style="400px" v-model="editData.desc"></el-input>
</div>
<br>
<div>
<span>日 期:</span>
<el-input class='elinput' style="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 {
120px;
height: 40px;
}
<style scoped>
</style>