nodejs实战开发,前后端分离项目框架搭建vue2+koa2+element+mysql


系统:Windows10 64位

版本对照
node v16.14.0
@vue/cli@5.0.4
cnpm@7.1.1
koa-generator@1.1.17
“axios”: “^0.26.1”,
“element-ui”: “^2.15.6”,
“vue”: “^2.6.14”,
“vue-router”: “^3.2.0”
“koa”: “^2.7.0”
“koa-cors”: “^0.0.16”,
“koa-router”: “^5.4.0”,
“mysql”: “^2.18.1”

一、准备工作

1.安装Node.js

官网下载地址:https://nodejs.org/en/download/
菜鸟安装教程:https://www.runoob.com/nodejs/nodejs-install-setup.html

以下命令在已配置node环境变量的 win+R cmd 窗口中运行

2.查看node版本(v16.14.0)

node -v

3.常用命令

//查看全局安装的插件
npm list  -g --depth 0 
//更新
npm  -g xxx
//删除
npm uninstall -g xxx
//安装
npm install -g xxx
//查看版本
npm view XXXX versions --json

4.安装淘宝镜像

npm install -g cnpm -registry=https://registry.npm.taobao.org

此项目并没有使用cnpm安装

二、vue-cli的安装使用

1.安装vue-cli

//3.0以下版本(默认安装最新版)
npm install vue-cli -g
//3.0以上版本(默认安装最新版)
npm install -g @vue/cli

//以下是安装指定版本
//3.0以下版本
npm install -g vue-cli@版本号
//3.0以上版本
npm install -g @vue/cli@版本号

2.查看vue-cli版本(@vue/cli 5.0.4)

vue -V

3.项目目录搭建

vue create 项目名
3.1 选择Y/N
 Your connection to the default npm registry seems to be slow.
   Use https://registry.npmmirror.com for faster installation? No
3.2 选择vue版本(此项选择了Vue 2)
Vue CLI v5.0.4
? Please pick a preset:
  Default ([Vue 3] babel, eslint)
> Default ([Vue 2] babel, eslint)
  Manually select features
4.运行项目(切换到项目根目录)
cd demo
npm run serve

在浏览器中输入 http://localhost:8080/

 INFO  Starting development server...

 DONE  Compiled successfully in 6047ms     21:14:15
  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.1.3:8080/

如下图:项目创建目录创建完成vue项目截图

三、Element安装与使用

1.安装element (^2.15.6)

官方使用教程 : https://element.eleme.cn/#/zh-CN/component/installation

npm i element-ui -S

2.在 main.js 中写入以下内容:

import Vue from 'vue';

/***引入element-ui*****/
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import 'element-ui/lib/theme-chalk/display.css';
Vue.use(ElementUI)
/*******************/

new Vue({
  el: '#app',
  render: h => h(App)
});

3.在vue组件使用ui

<template>
	<el-container>
		<el-header></el-header>
		<el-main></el-main>
		<el-footer></el-footer>
	</el-container>
</template>

四、vue-router的安装与使用

1.安装vue-router

npm install vue-router@3.2.0

注意当前项目使用的是vue2,和最新的vue-router不匹配,所以安装了vue-router@3.2.0

2.router的使用

2.1在项目src目录下新建router\index.js文件
import VueRouter from 'vue-router'
import Vue from 'vue'
	
import Index from '@/components/index.vue'
import Me from '@/components/navigation/components/me.vue'
import About from '@/components/navigation/components/about.vue'
import Login from '@/components/login.vue'
import Navigation from '@/components/navigation/navigation.vue'
	
const list = [{
	path: '/',
	component: Index,
	meta: {
		title: '首页'
		}
	}, {
		path: '/index', //首页
		name: 'index',
		component: Index,
		meta: {
			title: '首页'
		}
	},
	{
		path: '/login', //登录
		name: 'login',
		component: Login,
		meta: {
			title: '登录'
		}
	},
	{
		path: '/navigation', //导航栏
		name: 'navigation',
		component: Navigation,
		children: [ {
			path: '/about',
			name: 'about',
			component: About,
			meta: {
				title: '关于我们'
			}
		}, {
			path: '/me',
			name: '/me',
			component: Me,
			meta: {
				title: '个人中心'
			},
			]
		}],
	},
]
	
	
Vue.use(VueRouter)
const Router = new VueRouter({
	mode: 'history',
	routes: list
})
	
//修改动态网页标题 beforeEach 导航钩子,路由改变前触发
Router.beforeEach((to, from, next) => {
	window.document.title = to.meta.title;
	next();
})
	
// 路由跳转自动跳转到顶部
Router.afterEach((to, from, next) => {
	window.scrollTo(0, 0);
})	
export default Router
2.2 在main.js中引入router
import Vue from 'vue'
import App from './App.vue'
	
//引入vue-router
import router from '@/router'
	
new Vue({
	render: h => h(App),
	 router,
}).$mount('#app')

注意,在new Vue中注册router

3.路由的跳转

3.1在组件 <el-link>中 href 参数中写上path的值
<el-link href="/index" target="_blank">首页</el-link>
3.2在组件 <router-link> 中to参数中写上path的值
<router-link to="/index">首页</router-link>

4.渲染路径

<router-view>组件是一个 functional 组件,渲染路径匹配到的视图组件

<template>
	<div id="app">
		<router-view></router-view>
	</div>
</template>

五、使用koa-generator生成后端项目框架

Koa2生成器的使用:https://www.itying.com/koa/start-generator.html
通过应用koa脚手架生成工具 可以快速创建一个基于koa2的应用的骨架。

1. 安装koa-generator(@1.1.17)

npm install koa-generator -g

2. 查看koa-generator版本

koa -V

3. 创建一个命名为 koa_demo 的应用。

koa koa_demo

或者

koa2 koa_demo

koa和koa2中使用koa-router中间件的方式不一样,这里选择koa2

4. 运行koa_demo 应用

cd koa_demo 

npm install

npm start

通过 koa 应用生成器创建的应用一般都有如下目录结构:

7 directories, 9 files

├── app.js
├── bin
│ └── www
├── package.json
├── public
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.jade
├── index.jade
└── layout.jade

在浏览器中输入 http://localhost:3000

如下图:应用创建完成
koa2应用截图

六、Node.js 连接 MySQL

1.安装MySQL
cnpm install mysql
2.封装mysql
2.1在项目根目录下创建mysql\config.js

用来存放数据库的相关参数


const app = {
host     : '121.**.***.***',   // 数据库地址
user     : 'root',    // 数据库用户
password : '******' ,  // 数据库密码
database : 'baseName'  // 选中数据库

}

module.exports = app;
2.2 在项目根目录下创建 mysql\mysql.js
// sql查询等方法封装
const mysql = require('mysql')
const config = require("./config")

let pool = '' //这个应该也封装在类里面,奈何不会 

class Db {
    /**
     * 构造函数,创建数据池
     */
    constructor() {
        pool = mysql.createPool({
            host: config.host,
            user: config.user,
            password: config.password,
            database: config.database
        })
    }
	//在数据池中进行会话操作
    connect(sql, params) {
        return new Promise((resolve, reject) => {
            pool.getConnection((err, connection) => {
                if (err) {
                    reject(err)
                } else {
                    connection.query(sql, params, (error, result) => {
                        if (error) {
                            reject(error)
                        } else {
                            resolve(result)
                        }
                        // 结束会话
                        connection.release();
                    })
                }
            })

        })

    }
    //将参数连接成字符串
    paramsToStringAnd(params) {
        let str = '', temp = '', len = Object.keys(params).length
        if (len > 0) {
            Object.keys(params).forEach((paramsKey, index) => {
                if (len === 1 || index === len - 1) {
                    temp = paramsKey + '=' + params[paramsKey]
                } else {
                    temp = paramsKey + '=' + params[paramsKey] + ' and '
                }
                str = str + temp

            })
        }
        return str;
    }
   
    //将参数解封成,key,val,“?” 参考了菜鸟教程的调用方法
    paramsToStringSplit(params) {
        let str = '', tempKey = '', tempVal = "", placeholder = '', paramsArr = [], len = Object.keys(params).length
        Object.keys(params).forEach((paramsKey, index) => {
            if (len === 1 || index === len - 1) {
                tempKey = tempKey + paramsKey
                placeholder = placeholder + '?'
                tempVal = tempVal + params[paramsKey]
            } else {
                tempKey = tempKey + paramsKey + ','
                placeholder = placeholder + '?,'
                tempVal = tempVal + params[paramsKey] + ','
            }
            paramsArr.push(params[paramsKey])
        })
        console.log(tempKey);
        console.log(tempVal);
        console.log(placeholder);
        let res = {
            tempKey, tempVal, placeholder,paramsArr
        }
        return res;
    }
    //这个封装中其实有着一个方法就可以了,
    query(sql, params) {
        console.log("【"+sql+"】");
        console.log("params==",params)
        return new Promise((resolve, reject) => {
            this.connect(sql, params).then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })

        })

    }
    // 常用查询方法
    find(tableName, params) {
        let sql = '';
        console.log(Object.keys(params));
        if (Object.keys(params).length === 0) {
            sql = "select * from " + tableName
        } else {
            sql = "select * from " + tableName + " where " + this.paramsToStringAnd(params)
        }
        console.log("查询 sql:【" + sql + "】");
        return new Promise((resolve, reject) => {
            this.connect(sql).then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })

        })
    }
    //常用数据更新方法
    update() {
        let str = '', len = Object.keys(params).length
        const p =this.paramsToStringSplit(params)
        if (len > 0) {
            console.log(this.paramsToStringSplit(params));
            str = 'INSERT INTO ' + tableName + '(' + p.tempKey + ') VALUES(' + p.placeholder + ')'
            console.log(str);
            console.log(p.paramsArr);

        }
        return new Promise((resolve, reject) => {
            this.connect(str, p.paramsArr).then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })

        })
    }
    //常用数据增加方法
    insert(tableName, params) {
        let str = '', len = Object.keys(params).length
        const p =this.paramsToStringSplit(params)

        if (len > 0) {
            console.log(this.paramsToStringSplit(params));
            str = 'INSERT INTO ' + tableName + '(' + p.tempKey + ') VALUES(' + p.placeholder + ')'
            console.log(str);
           console.log(p.paramsArr);
        }
        return new Promise((resolve, reject) => {
            this.connect(str, p.paramsArr).then(res => {
                resolve(res)
            }).catch(err => {
                reject(err)
            })
        })
    }
	//常用数据删除方法,我一般不删除数据
}

module.exports = Db;


2.3 在routes目录下创建api.js

var router = require('koa-router')();
var Db = require('../mysql/mysql')

const myDb = new Db;

router.prefix('/api');
router.get('/', async (ctx, next) => {
    ctx.body = "this API"
})
//koa中使用koa-router
// router.get('/queryScenic', function* (next) {
//     console.log(this.query)
//     yield myDb.find("scenic", this.query).then((res) => {
//         this.body = {
//             code: 1,
//             data: res
//         }
//     }).catch(err => {
//         this.body = {
//             code: -1,
//             errMsg: err
//         }
//     })
// });
//
//koa2中这样写
router.get('/queryScenic', async (ctx, next) => {
    await myDb.find("scenic", ctx.query).then((res) => {
        console.log(typeof res);
        if (Object.keys(res).length > 0) {
            ctx.body = {
                code: 1,
                data: res
            }
        } else {
            ctx.body = {
                code: 0,
                data: "The data is empty"
            }
        }
    }).catch(err => {
        ctx.body = {
            code: -1,
            errMsg: err
        }
    })
})

/**
 * 获取地区列表
 * */
router.get('/queryCityList', async (ctx, next) => {
    let myDb = new Db;
    let pData = [], chData = [], data = [], m = [], N
    await myDb.query('select * from administrative', {}).then(rs => {
        if (Object.keys(rs).length > 0) {
            for (let i = 0; i < Object.keys(rs).length; i++) {
                console.log(rs[i], i);
                for (let j = 0; j < Object.keys(rs).length; j++) {
                    if (rs[i].id == rs[j].parent_id) {
                        rs[i]['children'] = rs.filter(item => {
                            if (rs[i].id == item.parent_id) {
                                return item
                            }
                        })
                        data.push(rs[i])
                        break;
                    }
                }
            }
            ctx.body = {
                code: 1,
                data: data
            }
        }else{
            ctx.body = {
                code: 0,
                data: "The data is empty"
            }
        }
    }).catch(err => {
        ctx.body = {
            code: -1,
            errMsg: err
        }
    })
})
/**
 * 获取兄弟城市*/
router.get("/querySiblingCity",async (ctx,next)=>{
    console.log(typeof ctx.query);
    console.log( ctx.query);
    let obj=ctx.query
    console.log(obj)
    console.log(obj.parent_id)

    if(!ctx.query){
        ctx.body={
            code:-1,
            data:ctx.query
        }
    }
    await myDb.query("select b.* from administrative a inner join administrative b on a.id=b.parent_id where b.parent_id="+obj.parent_id ).then((res) =>{
        if (Object.keys(res).length > 0) {
            ctx.body = {
                code: 1,
                data: res
            }
        } else {
            ctx.body = {
                code: 0,
                data: "The data is empty"
            }
        }

    })
})


module.exports = router;
3.在app.js文件中引入api.js
const api = require('./routes/api');
app.use(api.routes(), api.allowedMethods())
4.调用接口

在浏览器中输入 :
http://localhost:3000/api/querySiblingCity?parent_id=620000
http://localhost:3000/api/queryScenic?id=68

接口数据截屏

七、前端项目中axios的安装与使用

1.安装axios
npm install axios
2.封装axios请求
2.1在src目录新建axios\http.js
/**
 * axios封装
 * 请求拦截、响应拦截、错误统一处理
 */
import axios from 'axios';
import router from '../router';

/** 
 * 请求失败后的错误统一处理 
 * @param {Number} status 请求失败的状态码
 */
const errorHandle = (status, other) => {
	// 状态码判断
	switch (status) {
		case 200:
			console.log("成功");
			break;
		case 404:
			console.log("请求的资源不存在");
			break;
		default:
			console.log("other");
	}
}

// 创建axios实例
var instance = axios.create({
	timeout: 1000 * 12
});
// 设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
/** 
 * 请求拦截器 
 * 每次请求前,如果存在token则在请求头中携带token 
 */
instance.interceptors.request.use(
	config => {
		// 登录流程控制中,根据本地是否存在token判断用户的登录情况        
		// 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token        
		// 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码        
		// 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。        
		// const token = store.state.token;        
		// token && (config.headers.Authorization = token);
		return config;
	},
	error => Promise.error(error))

// 响应拦截器
instance.interceptors.response.use(
	// 请求成功
	res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),
	// 请求失败
	error => {
		const {
			response
		} = error;
		alert("请求错误:"+error)
		
		console.error(error)
		if (response) {
			// 请求已发出,但是不在2xx的范围 
			errorHandle(response.status, response.data.message);
			return Promise.reject(response);
		} else {
			// 处理断网的情况
			// eg:请求超时或断网时,更新state的network状态
			//。。。。。自己结合vueX实现
		}
	});

export default instance;
2.2在axios目录下创建api\base.js
/**
 * 接口域名的管理
 */
// http://localhost:3000/api/queryScenic?id=35
const base = {   
    public: 'http://121.84.22.267:3000/api',  //公网地址  
    local: 'http://localhost:3000/api' //本地地址
}

const baseUrl=base.local //不同的环境切换使用

export default baseUrl;
2.3在axios目录下创建api\index.js

用来引起其他api接口文件,这样在多人开发的项目中就可以单独使用某一模块,提供统一的接入口。

/** 
 * api接口的统一出口
 */
// 景点模块接口
import scenic from './scenic.js';
import city from './city.js'
// 其他模块的接口……
// 导出接口
export default {    
    scenic,
	 city
}
2.4 创建具体的api文件, src/axios/api/scenic.js
import baseUrl from './base'; // 导入接口域名列表
import axios from '../http.js'; // 导入http中创建的axios实例
const scenic = {
    // 景点列表
    scenicList(params) {
        let url = `${baseUrl}/queryScenic`
        console.log(url);
        console.log(params);
        return axios.get(url, {params})
    },
    // 其他接口…………
}
3. 在main.js中引入封装
import api from './axios/api' // 导入api接口
Vue.prototype.$api = api; // 将api挂载到vue的原型上
4.在组件中调用接口
created() {
	this.$api.scenic.scenicList({id: 38	})
		.then(res => {
			console.log(res);
			if (res.data.code == 1) {
				this.scienc_1 = res.data.data[0]
			}
		})
	this.$api.scenic.scenicList().then(res => {
		if (res.data.code == 1) {
			this.sciencList = res.data.data
		}
	})
},

八、解决跨域问题

1.安装koa2-cors( ^2.0.6),
npm install koa2-cors

koa项目安装koa-cors

2. 在后端项目app.js文件中
const cors = require('koa2-cors');
app.use(cors());

至此,一个本地可访问的前后端项目框架搭建完成,剩下的就是搬砖了,谢谢大家,一起学习交流,部分代码需要优化,还请指导

Logo

前往低代码交流专区

更多推荐