目錄css
新建一個文件夾book-curd-example
,如下先後端代碼都將在該文件夾下進行html
cnpm install vue-cli -g vue init webpack "client" //創建一個名稱爲client的前端項目 cnpm install // 安裝依賴 npm run dev
npm run dev
,在瀏覽器輸入http://localhost:8080/#
後顯示如下界面,則 client 項目生成完畢!book-curd-example
下新建一個文件夾server
文件夾用於保存後端代碼server
文件夾express
和其餘模塊npm install express body-parser cors morgan nodemon mysql2 sequelize --save
使用npm init -f
生成一個package.json
文件前端
修改成使用 nodemon 啓動vue
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon server.js" },
server.js
中寫入如下代碼用於測試,在server
文件夾下輸入npm start
啓動後臺程序const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const morgan = require('morgan'); const app = express(); app.use(morgan('combined')); app.use(bodyParser.json()); app.use(cors()); app.get('/posts', (req, res) => { res.send([ { title: 'Hello World!', description: 'Hi there! How are you?' } ]); }); app.listen(process.env.PORT || 8081);
8.在瀏覽器中訪問http://localhost:8081/posts
,顯示如下畫面,則後臺環境搭建成功。
node
字段 | 中文釋義 | 類型 | 是否可爲空 | 鍵 | 默認值 | 其餘 |
---|---|---|---|---|---|---|
id | 書籍 id | int(10) unsigned | NO | 主鍵 | null | auto_increment |
isbn | isbn 編號 | varchar(20) | NO | null | ||
name | 書名 | varchar(50) | NO | null | ||
author | 做者 | varchar(30) | NO | null | ||
出版社 | varchar(50) | null | ||||
publish_time | 出版日期 | date | null | |||
intro | 簡介 | varchar(255) | null | |||
remark | 備註 | varchar(200) | null |
DROP DATABASE IF EXISTS book_curd_example; CREATE DATABASE book_curd_example; use book_curd_example; DROP TABLE IF EXISTS book; CREATE TABLE IF NOT EXISTS `book`( `id` INT UNSIGNED AUTO_INCREMENT COMMENT '書籍id', `isbn` VARCHAR(20) NOT NULL COMMENT 'isbn編號', `name` VARCHAR(50) NOT NULL COMMENT '書名', `author` VARCHAR(30) NOT NULL COMMENT '做者', `print` VARCHAR(50) COMMENT '出版社', `publish_time` DATE COMMENT '出版日期', `intro` VARCHAR(255) COMMENT '簡介', `remark` VARCHAR(200)COMMENT '備註', PRIMARY KEY ( `id` ) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '圖書信息表';
/server/config/env.js
文件// 數據庫鏈接參數 const env = { database: 'book_curd_example', username: 'root', password: '123456', host: 'localhost', dialect: 'mysql', pool: { max: 5, min: 0, acquire: 30000, idle: 10000 } }; module.exports = env;
/server/config/db.config.js
文件const env = require('./env.js'); const Sequelize = require('sequelize'); const sequelize = new Sequelize(env.database, env.username, env.password, { host: env.host, dialect: env.dialect, operatorsAliases: false, pool: { max: env.max, min: env.pool.min, acquire: env.pool.acquire, idle: env.pool.idle } }); const db = {}; db.Sequelize = Sequelize; db.sequelize = sequelize; // 引入表模型 db.book = require('../model/book.model.js')(sequelize, Sequelize); module.exports = db;
sequelize-auto
模塊,利用sequelize-auto
模塊自動生成 book 表模型npm install -g sequelize-auto sequelize-auto -h localhost -d book_curd_example -u root -x 123456 -p 3306 -t book
2.複製生成的/models/book.js
文件,粘貼至/model
目錄下,並修改文件名爲/model/book.model.js
,刪除生成的models
目錄mysql
參考:Model definition - 模型定義 | sequelize-docs-Zh-CNwebpack
PS: 此處可能須要根據數據庫字段的屬性進行調整,好比自增屬性ios
/server/route/book.route.js
文件,用來定義接口// 圖書的增刪改查 module.exports = function(app) { const book = require('../controller/book.controller'); // 新增圖書信息 app.post('/book/add', book.create); // 刪除圖書 app.delete('/book/delete/:bookId', book.delete); // 根據id更新圖書信息 app.put('/book/update/:bookId', book.update); // 獲取圖書信息列表 app.get('/book/list', book.findAll); // 根據Id查詢圖書信息 app.get('/book/:bookId', book.findById); };
/server/controller/book.controller.js
const db = require('../config/db.config.js'); const Book = db.book; // 引入表模型 // 增長圖書 exports.create = (req, res) => { Book.create({ isbn: req.body.isbn, name: req.body.name, author: req.body.author, print: req.body.print, publish_time: req.body.publish_time, intro: req.body.intro, remark: req.body.remark }) .then(book => { let msg = { code: 200, msg: '新增成功!', id: book.id }; res.status(200).json(msg); }) .catch(err => { res.status(500).json('Error -> ' + err); }); }; // 刪除圖書 exports.delete = (req, res) => { const id = req.params.bookId; Book.destroy({ where: { id: id } }) .then(() => { let msg = { code: 200, msg: '刪除成功!' }; res.status(200).json(msg); }) .catch(err => { res.status(500).json('Error -> ' + err); }); }; // 更新圖書信息 exports.update = (req, res) => { const id = req.params.bookId; Book.update(req.body, { where: { id: req.params.bookId } }) .then(() => { let msg = { code: 200, msg: '修改信息成功!' }; res.status(200).json(msg); }) .catch(err => { res.status(500).json('Error -> ' + err); }); }; // 查詢全部圖書信息 exports.findAll = (req, res) => { Book.findAll() .then(book => { res.json(book); }) .catch(err => { res.status(500).json('Error -> ' + err); }); }; // 根據id查詢圖書信息 exports.findById = (req, res) => { Book.findById(req.params.bookId) .then(book => { res.json(book); }) .catch(err => { res.status(500).book('Error -> ' + err); }); };
server.js
服務器文件const express = require('express'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); const cors = require('cors'); const corsOptions = { origin: 'http://localhost:8080', optionSuccessStatus: 200 }; app.use(cors(corsOptions)); const morgan = require('morgan'); app.use(morgan('combined')); const db = require('./config/db.config'); require('./route/book.route')(app); // 建立服務器 let server = app.listen(process.env.PORT || 8081, () => { let host = server.address().address; let port = server.address().port; console.log('服務器啓動: http://%s:%s', host, port); });
使用postman
工具進行測試git
新建 5 個接口進行測試github
查詢單個實體數據接口測試
1.安裝axios
模塊
npm install axios --save
/src/utils/http.js
,引入封裝好的 axios 類import axios from 'axios' let httpInstance = axios.create() httpInstance.defaults.baseURL = 'http://localhost:8081/' httpInstance.defaults.timeout = 5000 httpInstance.formurl = (url, data, config) => { return httpInstance.post(url, data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, ...config }) }; // request攔截器 httpInstance.interceptors.request.use( config => { console.log(config) return config }, error => { return Promise.reject(error) } ) // reponse攔截器 httpInstance.interceptors.response.use( response => { if (response.status === 200) { return Promise.resolve(response) } }, error => { return Promise.reject(error) } ) export default httpInstance
main.js
中引入http.js
文件,並將其註冊爲 vue 全局變量import http from './utils/http' Vue.prototype.$http = http
element-ui
模塊npm install element-ui --save
main.js
中引入element-ui
模塊import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI)
book-list.vue
、book-detail.vue
、book-add.vue
。刪除原有的HelloWorld.vue
文件。router/main.js
中將路由修改以下import Vue from 'vue' import Router from 'vue-router' import BookList from '@/components/book-list' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'book-list', component: BookList } ] })
App.vue
文件中如下代碼<img src="./assets/logo.png">
book-list.vue
文件中寫入如下代碼<template> <div> Hello World! </div> </template> <script> export default {} </script> <style scoped> </style>
npm start
運行項目,在瀏覽器中訪問,則會出現Hello World
的文字book-list.vue
(1)效果圖
(2) 代碼
<template> <div> <header>圖書列表</header> <div class="container"> <div class="operate-btn"> <el-button @click="addBook">新增圖書</el-button> </div> <el-table :data="tableData" border style="width: 100%"> <el-table-column type="index"> </el-table-column> <el-table-column prop="name" label="圖書名稱" min-width="180px"> </el-table-column> <el-table-column prop="isbn" label="ISBN編號" min-width="180px"> </el-table-column> <el-table-column prop="author" label="做者" min-width="180px"> </el-table-column> <el-table-column prop="print" label="出版社" min-width="180px"> </el-table-column> <el-table-column prop="publish_time" label="出版日期" min-width="180px"> </el-table-column> <el-table-column label="操做" min-width="200px"> <template slot-scope="scope"> <el-button size="mini" @click="handleDetail(scope.$index, scope.row)">查看</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">刪除</el-button> </template> </el-table-column> </el-table> </div> <book-detail :bookId="bookId" :visible="bookDetailVisible" @closedDialog="closedDetailDialog"> </book-detail> <book-add :visible="bookAddVisible" @closedDialog="closeAddDialog" @addNewBook="addNewBook"> </book-add> </div> </template> <script> import BookDetail from './book-detail'; import BookAdd from './book-add'; export default { components: { BookDetail, BookAdd }, mounted () { this.getBookList() }, data () { return { tableData: [], bookId: null, bookDetailVisible: false, bookAddVisible: false } }, methods: { addNewBook (val) { this.bookId = val this.bookDetailVisible = true }, addBook () { this.bookAddVisible = true }, refreshBookList () { this.getBookList() }, closeAddDialog () { this.bookAddVisible = false this.refreshBookList() }, closedDetailDialog () { this.bookDetailVisible = false this.refreshBookList() }, handleDelete (index, row) { this.$http .delete(`/book/delete/${row.id}`) .then(res => { this.$message.success(res.data.msg) this.refreshBookList() }) .catch(err => { console.log('err=>', err) }) }, handleDetail (index, row) { this.bookId = row.id this.bookDetailVisible = true }, getBookList () { this.$http .get('/book/list') .then(res => { this.tableData = res.data }) .catch(err => { console.log('err->', err) }) } } } </script> <style scoped> header { font-size: 36px; height: 60px; padding-top: 30px; padding-left: 40px; box-shadow: 0px 15px 10px -15px #ccc; margin-bottom: 10px; } .container { text-align: center; box-shadow: 0px -15px 10px -15px #ccc; padding: 30px; } .el-table { padding-top: 20px; } .operate-btn { text-align: right; margin-bottom: 10px; } </style>
book-add.vue
(1)效果圖
(2)代碼
<template> <el-dialog :visible.sync="dialogVisible" @closed="closedDialog" min-width="360px"> <div slot="title"> <span class="title-name"> <span>新增圖書</span> </span> </div> <el-row> <el-col :span="4"> <div class="label">名稱</div> </el-col> <el-col :span="20"> <el-input v-model="bookInfo.name" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">ISBN編號</div> </el-col> <el-col :span="20"> <el-input v-model="bookInfo.isbn" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">做者</div> </el-col> <el-col :span="20"> <el-input v-model="bookInfo.author" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">出版社</div> </el-col> <el-col :span="20"> <el-input v-model="bookInfo.print" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">出版日期</div> </el-col> <el-col :span="20"> <el-date-picker v-model="bookInfo.publish_time" type="date" placeholder="選擇日期" size="medium"> </el-date-picker> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">簡介</div> </el-col> <el-col :span="20"> <el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4}" placeholder="請輸入內容" v-model="bookInfo.intro" max-length="200"> </el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">其餘</div> </el-col> <el-col :span="20"> <el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4}" placeholder="請輸入內容" v-model="bookInfo.remark" max-length="200"> </el-input> </el-col> </el-row> <div slot="footer" class="dialog-footer"> <el-button @click="cancelEdit" size="medium">取 消</el-button> <el-button type="primary" @click="addBook" size="medium">確 定</el-button> </div> </el-dialog> </template> <script> export default { props: { visible: { type: Boolean } }, watch: { visible: { handler (newV, oldV) { this.dialogVisible = newV } } }, mounted () {}, data () { return { dialogVisible: false, bookInfo: {} } }, methods: { addBook () { this.$http .post('/book/add', this.bookInfo) .then(res => { this.$message.success(res.data.msg) let bookId = res.data.id setTimeout(() => { this.$emit('addNewBook', bookId) this.closedDialog() }, 1000) }) .catch(err => { console.log('err=>', err) }) }, cancelEdit () { this.closedDialog() }, resetData () { this.dialogVisible = false this.bookInfo = {} }, closedDialog () { this.$emit('closedDialog') this.resetData() } } } </script> <style scoped> .el-row { line-height: 40px; margin-top: 10px; } .label { font-weight: bold; } .edit-btn { margin-left: 10px; } .title-name { font-size: 30px; } .dialog-footer { text-align: center; } </style>
book-detail.vue
(1)效果圖
(2)代碼
<template> <el-dialog :visible.sync="dialogVisible" @closed="closedDialog"> <div slot="title"> <span class="title-name">圖書信息</span> <el-button size="small" icon="el-icon-edit" round class="edit-btn" @click="editBookInfo">編輯</el-button> </div> <el-row> <el-col :span="4"> <div class="label">名稱</div> </el-col> <el-col :span="20"> <span v-if="!isEdit">{{bookInfo.name}}</span> <el-input v-model="bookInfo.name" v-if="isEdit" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">ISBN編號</div> </el-col> <el-col :span="20"> <span v-if="!isEdit">{{bookInfo.isbn}}</span> <el-input v-if="isEdit" v-model="bookInfo.isbn" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">做者</div> </el-col> <el-col :span="20"> <span v-if="!isEdit">{{bookInfo.author}}</span> <el-input v-if="isEdit" v-model="bookInfo.author" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">出版社</div> </el-col> <el-col :span="20"> <span v-if="!isEdit">{{bookInfo.print}}</span> <el-input v-if="isEdit" v-model="bookInfo.print" size="medium"></el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">出版日期</div> </el-col> <el-col :span="20"> <span v-if="!isEdit">{{bookInfo.publish_time}}</span> <el-date-picker v-if="isEdit" v-model="bookInfo.publish_time" type="date" placeholder="選擇日期" size="medium"> </el-date-picker> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">簡介</div> </el-col> <el-col :span="20"> <span v-if="!isEdit">{{bookInfo.intro}}</span> <el-input v-if="isEdit" type="textarea" :autosize="{ minRows: 2, maxRows: 4}" placeholder="請輸入內容" v-model="bookInfo.intro" max-length="200"> </el-input> </el-col> </el-row> <el-row> <el-col :span="4"> <div class="label">其餘</div> </el-col> <el-col :span="20"> <span v-if="!isEdit">{{bookInfo.remark}}</span> <el-input type="textarea" v-if="isEdit" :autosize="{ minRows: 2, maxRows: 4}" placeholder="請輸入內容" v-model="bookInfo.remark" max-length="200"> </el-input> </el-col> </el-row> <div slot="footer" class="dialog-footer" v-if="isEdit"> <el-button @click="cancelEdit" size="medium">取 消</el-button> <el-button type="primary" @click="updateBookInfo" size="medium">確 定</el-button> </div> </el-dialog> </template> <script> export default { props: { bookId: { type: Number }, visible: { type: Boolean } }, watch: { visible: { handler (newV, oldV) { this.dialogVisible = newV if (this.dialogVisible) { this.getBookById() } } } }, mounted () {}, data () { return { dialogVisible: false, bookInfo: {}, isEdit: false } }, methods: { refreshBookInfo () { this.getBookById() }, updateBookInfo () { this.$http .put(`/book/update/${this.bookId}`, this.bookInfo) .then(res => { console.log(this.$message) this.$message.success(res.data.msg) this.isEdit = false this.refreshBookInfo() }) .catch(err => { console.log('err->', err) this.isEdit = false }) }, cancelEdit () { this.isEdit = false }, resetData () { this.dialogVisible = false this.bookInfo = {} this.isEdit = false }, closedDialog () { this.$emit('closedDialog') this.resetData() }, getBookById () { this.$http .get(`/book/${this.bookId}`) .then(res => { this.bookInfo = res.data }) .catch(err => { console.log('err->', err) }) }, editBookInfo () { this.isEdit = true } } } </script> <style scoped> .el-row { line-height: 40px; margin-top: 10px; } .label { font-weight: bold; } .edit-btn { margin-left: 10px; } .title-name { font-size: 30px; } .dialog-footer { text-align: center; } </style>