zoukankan      html  css  js  c++  java
  • Vue+node.js+express+mysql实例---对图书信息进行管理

    一个简单的 CURD 实例 ---对图书信息进行管理

    目录

    1 开发环境

    • 前端:vue、axios
    • 后端:node.js、express
    • 数据库:mysql

    1.1 前端开发环境

    新建一个文件夹book-curd-example,以下前后端代码都将在该文件夹下进行

    1. 输入以下命令,安装 vue 框架
    cnpm install vue-cli -g
    vue init webpack "client" //建立一个名称为client的前端项目
    cnpm install //	安装依赖
    npm run dev
    
    1. 安装完毕之后,输入npm run dev,在浏览器输入http://localhost:8080/#后显示以下界面,则 client 项目生成完毕!
      vue初始化界面

    1.2 后端开发环境

    1. book-curd-example下新建一个文件夹server文件夹用于保存后端代码
    2. 进入server文件夹
    3. 安装express和其他模块
    npm install express body-parser cors morgan nodemon mysql2 sequelize --save
    
    • body-parser 解析
    • cors 跨域
    • morgan 日志记录
    • nodemon 程序调试自启
    • mysql2 mysql 数据库驱动管理工具
    • sequelize mysql-ORM 工具
    1. 安装完成之后建立以下目录和文件

    后台目录

    1. 使用npm init -f生成一个package.json文件

    2. 修改为使用 nodemon 启动

     "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "start": "nodemon server.js"
      },
    
    1. 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,显示以下画面,则后台环境搭建成功。
    后端环境准备成功

    2 数据库设计和创建

    2.1 数据库和表设计

    • 数据库名称:book_curd_example
    • 数据库表名称:book

    2.2 book 表设计

    字段 中文释义 类型 是否可为空 默认值 其他
    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
    print 出版社 varchar(50) null
    publish_time 出版日期 date null
    intro 简介 varchar(255) null
    remark 备注 varchar(200) null

    2.3 sql 语句编写

    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 '图书信息表';
    

    3 后台模块开发

    3.1 创建数据库连接

    1. 创建/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;
    
    
    
    
    1. 创建/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;
    
    
    

    3.2 创建表模型

    1. 安装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目录

    参考:Model definition - 模型定义 | sequelize-docs-Zh-CN

    PS: 此处可能需要根据数据库字段的属性进行调整,比如自增属性

    3.3 编写接口

    1. 创建/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);
    };
    
    
    1. 创建控制器文件/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);
        });
    };
    
    
    1. 修改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);
    });
    
    

    3.4 接口测试

    使用postman工具进行测试

    1. 新建 5 个接口进行测试

      新建5个接口用于测试

    2. 新增数据接口测试
      新增数据
      新增结果

    3. 删除数据接口测试
      删除数据

    4. 修改数据接口测试
      修改数据

    5. 查询所有数据接口测试
      查询所有数据

    6. 查询单个实体数据接口测试
      查询单个实体数据

    4 前端模块开发

    4.1 安装并引入前端开发所需外部模块

    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
    
    1. 安装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)
    

    4.2 建立路由

    1. 建立文件
      在 components 下新建 3 个文件book-list.vuebook-detail.vuebook-add.vue。删除原有的HelloWorld.vue文件。
    2. 修改路由
      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
        }
      ]
    })
    
    
    1. 删除App.vue文件中以下代码
    <img src="./assets/logo.png">
    
    1. book-list.vue文件中写入以下代码
    <template>
      <div>
        Hello World!
      </div>
    </template>
    <script>
    export default {}
    </script>
    <style scoped>
    </style>
    
    
    1. 使用npm start运行项目,在浏览器中访问,则会出现Hello World的文字

    4.3 编写组件

    1. 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=" 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>
    
    
    1. 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>
    
    
    1. 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>
    
    
  • 相关阅读:
    ASP.NET Routing Debugger
    浏览器 CSS & JS Hack 手册
    基于vmWare5.5环境的VxWorks系统安装总结
    TFS 迁移到 Git
    关于websocket
    自定义单一模块Model类
    学习 C++的用途
    Navigation Controllers and Table Views(中)
    Mac环境下svn的使用
    减少.NET应用程序内存占用的一则实践
  • 原文地址:https://www.cnblogs.com/yejingping/p/10413591.html
Copyright © 2011-2022 走看看