本文记录一下实现一个全栈小我的项目,前端应用vue框架、后端应用express框架、数据库应用mysql。

产品需要剖析

产品经理说,我须要做一个web人员治理我的项目,我的项目蕴含以下性能:

  • 用户能够在页面上新建数据,新建的数据内容有,人名、年龄、他乡、以及此人的备注
  • 用户能够删除之前新建的人员信息,删除只做逻辑删除,不做物理删除(不删数据库数据)
  • 用户能够批改之前新建的人员信息,人名、年龄、他乡、备注均能够批改
  • 页面中要有一个输入框做含糊搜寻性能,比方用户搜寻一个“海”这个字,所有和这个字关联的数据都要出现筛选进去。无论人名、他乡、还是备注中蕴含了这个字。
  • 用户能够勾选行,做excel导出性能
  • 应用表格出现数据,做分页治理、所有字段都反对排序
说白了,就是增删改查分页排序导出性能。咱们理解需要当前,就能够设计数据库表、以及字段了

预览一下效果图

开始设计数据库之前,咱们先预览最终的效果图,最终的前后端代码以及mysql表,文末会给到Gitee的地址,欢送大家下载瞅瞅看看

数据库设计

这里因为性能比较简单,所以咱们设计一张表即可,在一张表里存储我的项目中须要的字段即可。当然建表之前,咱们要先新建一个数据库。新建数据库之前,咱们要连贯本人的MySql数据库。这里我用的是Navicat工具治理Mysql数据库。

连贯数据库

新建数据库

数据库的名字叫做person_manage,后续express中配置数据库连接池须要用到。

新建表(新建字段)

新建的这张表的名字是people_table

这里就不赘述,Mysql的下载和Navcat的下载了。大家谷歌(百度)一下,有很多下载安装的教程的。

前端页面开发

前端应用vue我的项目,所以咱们从0到1简略搭建一个vue我的项目。

  • 提前装置好node软件、node中自带npm node -v和npm-v别离查看对应版本号,我的版本别离是v12.18.0和6.14.4
  • 配置淘宝镜像 npm install -g cnpm –registry=https://registry.npm.taobao.org,这样下载包的时候会快一些
  • 应用vue-cli疾速生成一个vue我的项目架构。其实vue-cli脚手架其实也是一个npm包,所以要应用npm下载vue-cli脚手架,执行命令npm install -g @vue/cli全局下载vue-cli脚手架,执行命令vue -V能够查看相应的vue-cli的版本号
  • 最初执行vue create 我的项目名创立一个我的项目,这里我创立的是mydemo我的项目,即vue create mydemo

我的项目创立好了当前,咱们还须要下载axios用法发申请,下载vue-router用来做路由,下载element-ui用来疾速开发,下载nprogress做个进度条等相干插件包。而后引入并应用这些包,最初咱们依照我的项目须要,批改一下我的项目的目录,最终是上面的构造。

前端我的项目结构图

接着贴出各个文件夹代码

api文件夹局部

axios的二次封装(api.js)

// 引入axios包,并创立一个axios实例import axios from "axios";  const http = axios.create({})import NProgress from 'nprogress' // 引入NProgress 当然,要提前npm下载一下import 'nprogress/nprogress.css'  // 引入NProgress和对应的款式NProgress.configure({ showSpinner: false }) // 暗藏右侧的旋转进度条// 给这个axios实例加上申请拦截器和相应拦截器//申请拦挡http.interceptors.request.use(    (config) => {        NProgress.start() // 开启进度条        return config;    },    (err) => {        return Promise.reject(err);    })//响应拦挡http.interceptors.response.use(    (res) => {        NProgress.done() // 敞开进度条        return res.data    },    (error) => {        NProgress.done() // 敞开进度条        return Promise.reject(error);    })/*    裸露一个函数,在函数中应用刚创立的且加上拦截器的axios实例去发申请。    因为发申请须要指定申请形式、申请地址、申请参数、申请头、响应类型等相干信息    所以,须要接管相应的method、url、data、headers、responseType等参数    这里最终裸露的api函数是留给lss/index.js文件中定义的接口函数应用的*/ export default (method, url, data = null, headers = 'application/json;charset=UTF-8', responseType) => {    if (method == "post") {        return http({            method: 'post',            url: url,            data: data,            headers: {                'Content-Type': headers,            },            responseType: responseType        });    } else if (method == "get") {        return http({            method: 'get',            url: url,            params: data,            headers: {                'Content-Type': headers            },            responseType: responseType        });    } else if (method == "put"){        // ......        return;    } else if (method == "delete"){        // .....        return;    } }
axios的二次封装当前,咱们须要在接口函数中引入,并定义接口函数

接口函数的定义(lss/index.js)

// 导入http中创立的axios实例import http from '../api';export const getTableData = (params) => http("get", `/api/getTableData`,params)//分页查问获取人物信息export const getTotalCount = () => http("get", `/api/getTotalCount`)//分页查问获取人物信息总条目数export const deleteData = (params) => http("get", `/api/deleteData`,params)//删除人物信息export const addData = (params) => http("post", `/api/addData`,params)//新增人物信息export const editData = (params) => http("post", `/api/editData`,params)//编辑人物信息export const exportExcel = (params) => http("post", `/api/exportExcel`,params,'application/json; charset=UTF-8',"arraybuffer")//导出表格数据
接口函数定义了当前,咱们在(api/index.js)文件中做对立治理,裸露进来,在main.js文件中再引入最终注册到Vue的原型链下来。

接口函数的对立治理并裸露(api/index.js)

// 对立治理理一下申请接口import {    getTableData,    getTotalCount,    deleteData,    addData,    editData,    exportExcel,} from './lss/index'export default {    getTableData,    getTotalCount,    deleteData,    addData,    editData,    exportExcel,}

main.js中将接口函数注册到Vue原型上

看倒数第五行、倒数第六行
import Vue from 'vue'import App from './App.vue'Vue.config.productionTip = false // 敞开提醒import "./assets/css/reset.css"; //引入重置样式表import ElementUI from 'element-ui'; //引入饿了么包import 'element-ui/lib/theme-chalk/index.css'; //引入饿了么款式Vue.use(ElementUI); // 应用饿了么UIimport router from './router/index' // 引入路由import api from "./api/index" // 引入封装的axios的接口函数Vue.prototype.$api = api // 将封装的axios接口函数注册到原型链上new Vue({    render: h => h(App),    router // 挂载路由}).$mount('#app')

assets文件夹局部

assets文件夹寄存的个别是动态资源文件,比方重置样式表,比方404页面的图片等。当然重置样式表,也是要在main.js中引入并应用的。这里就不赘述了

router文件夹局部(router.index.js)

路由表咱们这样配置,因为小我的项目,所以只做两个页面。homepage页面和404页面接口,代码如下:

import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter) // 注册路由const router = new VueRouter({    mode: 'history', // hash    routes: [        {            path: "/",            redirect: "/homepage",        },        {            path: "/homepage",            component: resolve => require(['@/views/homepage.vue'], resolve),            meta: {                name: "homepage"            }        },        {            // 会匹配所有门路,这里定义一个404页面            path: '*',            component: resolve => require(['@/views/404.vue'], resolve),            meta: {                name: "404"            },            // redirect: "/homepage",  // 当然,也能够重定向到主页页面        }    ]})export default router
当然路由表也是要引入到main.js中挂载到vue实例下来的。

views文件夹局部

views文件夹寄存的是咱们所写的页面对应的vue相干组件,这里就不赘述了。次要就是留神,路由表的层级和router-view的层级对应

因为nodejs次要是咱们前端应用去做后端的事件,所以,就用多点篇幅讲述nodejs框架express的应用

后端接口开发

咱们后端应用的是express框架,这里为了让过程清晰点,就不应用express-generator这个插件了,咱们一步步的书写即可。

express-generator是一个node的自动化创立我的项目工具,相似于vue-cli,也算是一个express脚手架

搭建我的项目步骤

第一步 npm初始化我的项目

执行npm init --yes会创立一个根底的我的项目,会在文件夹中生成一个package.json文件,命令行会有如下提醒,图片如下:

第二步 创立app.js入口文件

装置相干的依赖包,这里咱们应用express框架、应用nodemon插件,不必重启后端服务、应用mysql插件连贯数据库、应用node-xlsx插件做表格导出。所以执行如下命令:cnpm i express nodemon mysql node-xlsx --save。npm命令执行当前,package.json文件就会把相干的包增加到依赖中,同时也会多一个node_modules文件夹,用于寄存咱们下载的相干包。如下:

// package.json文件{  "name": "expressdemo",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "test": "echo \"Error: no test specified\" && exit 1"  },  "keywords": [],  "author": "",  "license": "ISC",  "dependencies": {    "express": "^4.17.1",    "mysql": "^2.18.1",    "node-xlsx": "^0.17.1",    "nodemon": "^2.0.12"  }}

第三步 先创立一个简略的服务

在与package.json文件同级目录上,创立一个app.js文件,这个文件是express服务入口文件。贴上如下代码:

const express = require('express') // 引入express包const app = express() // 创立express实例app.get('/', (req, res) => { // 当申请为get申请,url门路为 / 的时候,返回如下数据    res.send('这个是用express搭建的服务')})// 在9999端口上启动后端服务app.listen(9999, (req, res) => {    console.log('后端服务端口地址为:localhost://9999');})

这样的话,一个简略的后端服务就搞好了。咱们在浏览器地址栏,本机localhost:9999拜访一下,就能够看到成果啦,如下图:

当然理论的后端服务不会怎么简略,所以咱们须要再增加代码。
  • 比方app.js中治理路由要做模块化拆分(中间件模式)app.use(Router)
  • 比方须要应用应用body-parser中间件做post申请的参数解析cnpm i body-parser
  • 比方须要应用mysql插件做数据库连贯治理cnpm i mysql
  • 比方我的项目中有下载导出表格性能须要应用cnpm i mysql node-xlsx
  • 当然,为了不便后端调试,咱们也顺带下载nodemon用一下cnpm i mysql nodemon

第四步 最终的express目录和代码

后端服务目录图如下

package.json文件

/* package.json文件配置信息 */{  "name": "expressdemo",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "test": "echo \"Error: no test specified\" && exit 1"  },  "author": "",  "license": "ISC",  "dependencies": {    "body-parser": "^1.19.0",    "express": "^4.17.1",    "mysql": "^2.18.1",    "node-xlsx": "^0.17.1",    "nodemon": "^2.0.12"  }}

app.js程序主入口文件

/* app.js文件 */// 引入express插件包并生成一个实例appconst express = require('express')const app = express()// 应用body-parser中间件解析post申请主体app.use(express.urlencoded({ extended: false }))app.use(express.json())const Router = require('./router') // 引入分模块治理的路由// 路由分模块app.use(Router) // 在9999端口上启动后端服务app.listen(9999, (req,res) => {    console.log('后端服务端口地址为:localhost://9999');})

mysql连接池配置文件

/* sql/index.js文件 */// 引入mysql数据库var mysql = require('mysql')// 数据库连接池的配置var pool = mysql.createPool({  connectionLimit: 10,  // 连接池的大小  host: 'localhost', // 主机名  user: 'root',  // 用户名  password: '123456', // 明码  database: 'person_manage' // 数据库名称 在数据库外面建设了一个person_manage数据库,外面有很多表格});// 裸露连接池module.exports = pool

接口开发

咱们在router.js文件中写接口

分页排序接口(带含糊搜寻查问)

const express = require('express') // 引入expressconst route = express.Router() // 实例化一个路由对象// 引入node-xlsx包const xlsx = require('node-xlsx') // 引入连接池const pool = require('./sql/index')// 分页排序接口(带有含糊搜寻查问)route.get('/getTableData', (req, res) => {  // console.log('申请参数', req.query);  /* 个别的分页参数一共有四个:排序字段、排序形式、第几页、每页几条 */  // 分页查问格局: select * from table limit (start-1)*limit,limit;  // let sql = "select * from people_table ORDER BY age ASC LIMIT 0,2"  // let sql = "select * from people_table"  // 又因为这个接口带有含糊查问,所以应用like含糊查问searchWord  let sql = null // 拼接sql语句  if (req.query.sortWord == "age" | req.query.sortWord == "id") {                                                                                                                                         // 数字类型的应用默认排序 比方年龄和id    sql = `select * from people_table WHERE (name LIKE '%${req.query.searchWord}%' OR home LIKE '%${req.query.searchWord}%' OR remark LIKE '%${req.query.searchWord}%') AND is_delete_status <> 0 ORDER BY ${req.query.sortWord} ${req.query.sortOrder} LIMIT ${(req.query.pageIndex - 1) * (req.query.pageSize)},${req.query.pageSize}`  } else {                                                                                                                                                                                                    // 汉字类型的,强制应用GBK排序,这样的话,就可能依照汉语拼音排序了    sql = `select * from people_table WHERE (name LIKE '%${req.query.searchWord}%' OR home LIKE '%${req.query.searchWord}%' OR remark LIKE '%${req.query.searchWord}%') AND is_delete_status <> 0 ORDER BY CONVERT( ${req.query.sortWord} USING gbk ) ${req.query.sortOrder} LIMIT ${(req.query.pageIndex - 1) * (req.query.pageSize)},${req.query.pageSize}`  }  // console.log('拼接好的sql语句',sql);  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(sql, function (error, results, fields) {      connection.release()      let apiRes = {        code: 0,        msg: "胜利",        data: results      }      res.send(apiRes)    })  })})// 其余接口...module.exports = route // 裸露进来方便管理

查问总条目数接口

route.get("/getTotalCount", (req, res) => {  console.log(req.query);  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(`select count(*) AS total from people_table WHERE (name LIKE '%${req.query.searchWord}%' OR home LIKE '%${req.query.searchWord}%' OR remark LIKE '%${req.query.searchWord}%') AND is_delete_status <> 0`, function (error, results, fields) {      connection.release()      let apiRes = {        code: 0,        msg: "胜利",        data: results[0].total      }      res.send(apiRes)    })  })})

逻辑删除数据接口(单条删除)

route.get("/deleteData", (req, res) => {  let sql = `UPDATE people_table SET is_delete_status = 0 WHERE id = ${req.query.id}`  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(sql, function (error, results, fields) {      connection.release()      if (results.affectedRows == 1) { // 阐明影响了一行,即 将逻辑删除字段批改了,删掉了一行        let apiRes = {          code: 0,          msg: "胜利",          data: "祝贺您,删除胜利啦..."        }        res.send(apiRes)      } else {        let apiRes = {          code: 0,          msg: "胜利",          data: "很道歉,删除失败了..."        }        res.send(apiRes)      }    })  })})

物理删除数据接口(单条删除)

route.get("/trueDeleteData", (req, res) => {  let sql = `DELETE from people_table WHERE id = ${req.query.id}`  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(sql, function (error, results, fields) {      connection.release()      if (results.affectedRows == 1) { // 阐明影响了一行,即 真删掉了一行        let apiRes = {          code: 0,          msg: "胜利",          data: "祝贺您,删除胜利啦..."        }        res.send(apiRes)      } else {        let apiRes = {          code: 0,          msg: "胜利",          data: "很道歉,删除失败了..."        }        res.send(apiRes)      }    })  })})

逻辑删除数据接口(批量删除)

route.get("/selectDelete",(req,res)=>{  console.log(req.query.ids);  let sql = `UPDATE people_table SET is_delete_status = 0 WHERE id in (${req.query.ids})`  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(sql, function (error, results, fields) {      console.log('后果',results);      connection.release()      if (results.affectedRows != 0) { // 阐明影响了一行,即 将逻辑删除字段批改了,删掉了一行        let apiRes = {          code: 0,          msg: "胜利",          data: "祝贺您,批量删除胜利啦..."        }        res.send(apiRes)      } else {        let apiRes = {          code: 0,          msg: "失败",          data: "很道歉,批量删除失败了..."        }        res.send(apiRes)      }    })  })})

新增数据接口

/** * 值得一提的是,我这里没有做封装sql语句函数 * 所以一些sql语句是动静拼接下来的 * 理论工作中还是要依据我的项目需要封装一下sql语句的 * 次要是不便能一步步的看懂 * */ route.post("/addData", (req, res) => {  // console.log('新增接口申请参数',req.body);  // console.log('新增接口申请参数',Object.keys(req.body));  // console.log('新增接口申请参数',Object.values(req.body));  // 正确sql语句: INSERT INTO myTale(NAME,sex,borndate) VALUES('白骨精','女','2021-05-16');  // 谬误sql语句: INSERT INTO myTale(NAME,sex,borndate) VALUES(白骨精,女,2021-05-16);  // 因为往数据库中写入数据须要留神引号的问题,即VALUES()中须要带上引号,所以下方的拼接sql语句要应用转义字符 \'   let str = ""  // 这里大家能够打断点、或者打印看看就知道了  Object.values(req.body).forEach((item) => {    str = str + "\'" + item + "\'" + ","  })  // console.log('截取一下',str.substr(0,str.length -1)); // 截取一下,不要最初的一个逗号  let editStr = str.substr(0, str.length - 1)  let sql = `INSERT INTO people_table (${Object.keys(req.body).toString()}) VALUES(${editStr})`  // console.log("看看应用转义字符拼接好的sql语句", sql);  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(sql, function (error, results, fields) {      connection.release()      if (results.affectedRows == 1) {        let apiRes = {          code: 0,          msg: "胜利",          data: "祝贺您,新增胜利啦..."        }        res.send(apiRes)      } else {        let apiRes = {          code: 0,          msg: "失败",          data: "道歉新增失败"        }        res.send(apiRes)      }    })  })})

编辑数据接口

route.post("/editData", (req, res) => {  // 将参数加工一下  let id = req.body.id  delete req.body.id  // console.log('原始参数', req.body);  // console.log('keys数组参数', Object.keys(req.body));  // console.log('values数组参数', Object.values(req.body));  let keysArr = Object.keys(req.body)  let valuesArr = Object.values(req.body)  let str = ""  for (let i = 0; i < keysArr.length; i++) {    for (let j = 0; j < valuesArr.length; j++) {      if (i == j) {        str = `${str},${keysArr[i]}=${"\'" + valuesArr[i] + "\'"}`      }    }  }  str = str.substr(1, str.length)  let sql = `UPDATE people_table SET ${str} WHERE id=${id}`  // console.log("看看拼接好的sql语句-->",sql);  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(sql, function (error, results, fields) {      connection.release()      console.log('编辑数据库后果--->', results);      if (results.affectedRows == 1) {        let apiRes = {          code: 0,          msg: "胜利",          data: "祝贺您,编辑胜利啦..."        }        res.send(apiRes)      } else {        let apiRes = {          code: 0,          msg: "失败",          data: "道歉编辑失败了!"        }        res.send(apiRes)      }    })  })})

excel勾选导出接口

const xlsx = require('node-xlsx') // 引入node-xlsx包 route.post('/exportExcel', (req, res) => {  let sql = `SELECT name,home,age,remark FROM people_table WHERE FIND_IN_SET(id,'${req.body.ids}')`  // console.log('拼接好的sql语句-->', sql);  pool.getConnection(function (err, connection) {    if (err) { throw err }    connection.query(sql, function (error, results, fields) {      connection.release()      // console.log('编辑数据库后果--->', results);      let data = []      data.push(Object.keys(results[0])) // excel表格表头      results.forEach((item) => {        data.push(Object.values(item))      })      // console.log('加工的data数据',data);      let sheetArr = [ // excel表格内容数据        {          name: "sheet123", // 指定sheet的名字          data: data // 对应sheet的内容        },      ]      let optionArr = { // excel表格内容配置数据        "!cols": [          { wch: 15 },          { wch: 15 },          { wch: 10 },          { wch: 30 },        ],      }      // build办法用来生成一个表格,并以二进制文件的模式传递给前端      // 这里要用end办法,如果应用send办法就会报错      res.end(xlsx.build(sheetArr, optionArr))    })  })})

excel导出能够参考之前的文章:https://segmentfault.com/a/11...

前后端联调

前后端联调就比较简单了,前端依据后端提供的api接口,发申请给后端,后端接到相应的申请当前,再去做相应的数据库操作,最终返回相应的数据给前端。这里就不赘述了。具体的状况能够clone我上传到Gitee的代码哈,能够棘手给个star哈,谢谢喽

我的项目Gitee地址

  • 前端代码地址是:https://gitee.com/ah-shuai/de...
  • 后端代码地址是:https://gitee.com/ah-shuai/ex...

总结

我集体感觉前端的道友们,还是要学一下node和mysql的,毕竟学好node和mysql当前,整个我的项目的流程就跑通了,咱们就会有整体的思维。而且有助于咱们配合后端联调我的项目,晋升我的项目整体开发效率。

工作空闲之余,抽空写的一个全栈我的项目,可能不太欠缺,欢送各位大佬批评指正,多交换、共提高哈