看了些 koa2 与 Vue2 的资料,模仿着做了一个基本的后台管理系统,包括增、删、改、查与图片上传。

工程目录:

由于 koa2 用到了 async await 语法,所以 node 的版本需要至少 v7.6.0,我目前用的是 v7.9.0

1. 根据 package.json 安装好依赖:

{
"name": "vue2.x-koa2.x",
"version": "1.0.0",
"description": "A Vue.js and Koa project",
"author": "caihuaguang@aixuedai.com <caihuaguang@aixuedai.com>",
"private": true,
"scripts": {
"server": "node app.js",
"dev": "node build/dev-server.js",
"build": "node build/build.js"
},
"dependencies": {
"axios": "^0.15.3",
"bcryptjs": "^2.4.0",
"busboy": "^0.2.14",
"element-ui": "^1.2.7",
"koa": "^2.2.0",
"koa-bodyparser": "^4.2.0",
"koa-history-api-fallback": "^0.1.3",
"koa-jwt": "^1.3.1",
"koa-logger": "^2.0.1",
"koa-router": "^5.4.0",
"koa-static": "^3.0.0",
"mysql": "^2.12.0",
"sequelize": "^3.30.4",
"stylus": "^0.54.5",
"stylus-loader": "^2.4.0",
"vue": "^2.2.6",
"vue-router": "^2.3.0",
"vuex": "^2.2.1"
},
"devDependencies": {
"autoprefixer": "^6.4.0",
"babel-core": "^6.24.0",
"babel-loader": "^6.4.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.0",
"babel-preset-stage-0": "^6.22.0",
"babel-register": "^6.24.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.1.0",
"css-loader": "^0.25.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.13.3",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"friendly-errors-webpack-plugin": "^1.1.2",
"function-bind": "^1.0.2",
"html-webpack-plugin": "^2.8.1",
"http-proxy-middleware": "^0.17.2",
"json-loader": "^0.5.4",
"opn": "^4.0.2",
"ora": "^0.3.0",
"semver": "^5.3.0",
"shelljs": "^0.7.4",
"url-loader": "^0.5.7",
"vue-loader": "^10.0.0",
"vue-style-loader": "^1.0.0",
"vue-template-compiler": "^2.1.0",
"webpack": "^1.9.11",
"webpack-dev-middleware": "^1.8.3",
"webpack-hot-middleware": "^2.12.2",
"webpack-merge": "^0.14.1"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
}
}

2. mysql 数据库:可以单独安装,也可以用集成工具 WampServer,我用的是后者。

数据库用可视化工具 HeidiSQL 来操作。

建一个数据库 my_test_db,字符集选择 utf8_unicode_ci(之前没注意的时候,直接用了服务器默认的 latin1_swedish_ci,导致数据库不能保存中文)

my_test_db 中新建两张表 user(用户) 与 goods(商品)

user 表相关字段:

goods 表相关字段:

可以先手动输入一些数据。

3. 将数据库的表结构导出到 schema 文件夹

sequelize-auto -o "./schema" -d my_test_db -h 127.0.0.1 -u root -p 3306 -x 123456 -e mysql

其中,root是数据库用户名,123456是密码。sequelize-auto 具体用法参考这里

成功后会生成两个文件 user.js 与 goods.js

module.exports = function(sequelize, DataTypes) {
return sequelize.define('user', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
user_name: {
type: DataTypes.CHAR(50),
allowNull: false
},
password: {
type: DataTypes.CHAR(128),
allowNull: false
}
}, {
tableName: 'user'
});
};

user 表结构

module.exports = function(sequelize, DataTypes) {
return sequelize.define('goods', {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.CHAR(50),
allowNull: false
},
description: {
type: DataTypes.CHAR(200),
allowNull: true
},
img_url: {
type: DataTypes.STRING,
allowNull: true
}
}, {
tableName: 'goods'
});
};

goods 表结构

4. 连接数据库

在 server/config 目录添加 db.js,内容如下:

const Sequelize = require('sequelize');

// 使用 url 形式连接数据库
const theDb = new Sequelize('mysql://root:123456@localhost/my_test_db', {
define: {
timestamps: false // 取消Sequelzie自动给数据表添加的 createdAt 和 updatedAt 两个时间戳字段
}
}) module.exports = {
theDb
}

sequelize 具体用法查看这里

(一)用户

5. 操作数据库

在 server/models 目录添加 user.js

const theDatabase = require('../config/db.js').theDb;
const userSchema = theDatabase.import('../schema/user.js'); // 通过用户名查找
const getUserByName = async function(name) {
const userInfo = await userSchema.findOne({
where: {
user_name: name
}
}) return userInfo
} // 通过用户 id 查找
const getUserById = async function(id) {
const userInfo = await userSchema.findOne({
where: {
id: id
}
}); return userInfo
} const getUserList = async function() {
return await userSchema.findAndCount(); // findAndCount() 用 get 路由访问,会得到 204 状态:无数据返回。改用 post 就行
} module.exports = {
getUserByName,
getUserById,
getUserList
}

查找 user 表

findOne 与 findAndCount 都可以查询数据库,其本质是对 select 语句的封装

6. 服务端具体业务代码

在 server/controllers 目录添加 user.js

 const userModel = require('../models/user.js');
const jwt = require('koa-jwt');
const bcrypt = require('bcryptjs'); const postUserAuth = async function() {
const data = this.request.body; // 用 post 传过来的数据存放于 request.body
const userInfo = await userModel.getUserByName(data.name); if (userInfo != null) { // 如果查无此用户会返回 null
if (userInfo.password != data.password) {
if (!bcrypt.compareSync(data.password, userInfo.password)) {
this.body = { // 返回给前端的数据
success: false,
info: '密码错误!'
}
}
} else { // 密码正确
const userToken = {
id: userInfo.id,
name: userInfo.user_name,
originExp: Date.now() + 60 * 60 * 1000, // 设置过期时间(毫秒)为 1 小时
}
const secret = 'vue-koa-demo'; // 指定密钥,这是之后用来判断 token 合法性的标志
const token = jwt.sign(userToken, secret); // 签发 token
this.body = {
success: true,
token: token
}
}
} else {
this.body = {
success: false,
info: '用户不存在!'
}
}
} const getUserInfo = async function() {
const id = this.params.id; // 获取 url 里传过来的参数里的 id
const result = await userModel.getUserById(id);
this.body = result
} const getUserList = async function() {
const result = await userModel.getUserList(); this.body = {
success: true,
total: result.count,
list: result.rows,
msg: '获取用户列表成功!'
}
} module.exports = {
postUserAuth,
getUserInfo,
getUserList
}

user 控制器

7. 定义接口,用于前端发送 ajax 的 url

在 server/routes 目录添加 user.js

const userController = require('../controllers/user.js');
const router = require('koa-router')(); router.post('/user', userController.postUserAuth);
router.get('/user/:id', userController.getUserInfo); // 定义 url 的参数 id
router.post('/user/list', userController.getUserList); module.exports = router;

这是登录的后端部分。

8. 在根目录添加 app.js

const path = require('path'),
koa = new (require('koa'))(),
koaRouter = require('koa-router')(),
logger = require('koa-logger'),
koaStatic = require('koa-static'),
historyApiFallback = require('koa-history-api-fallback'),
image = require('./server/routes/image.js'),
user = require('./server/routes/user.js'),
goods = require('./server/routes/goods.js'); koa.use(require('koa-bodyparser')());
koa.use(logger());
koa.use(historyApiFallback()); koa.use(async (ctx, next) => {
let start = new Date();
await next();
let ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
}); koa.on('error', function(err, ctx) {
console.log('server error: ', err);
}); // 静态文件 koaStatic 在 koa-router 的其他规则之上
koa.use(koaStatic(path.resolve('dist'))); // 将 webpack 打包好的项目目录作为 Koa 静态文件服务的目录 // 挂载到 koa-router 上,同时会让所有的 user 的请求路径前面加上 '/auth' 。
koaRouter.use('/auth', user.routes()); koaRouter.use(goods.routes()); koaRouter.use(image.routes()); koa.use(koaRouter.routes()); // 将路由规则挂载到Koa上。 koa.listen(8889, () => {
console.log('Koa is listening on port 8889');
}); module.exports = koa;

在命令行中执行 npm run server,正常情况下应该会成功(前提是 mysql 已经正常启动)

9. 前端部分

src/main.js

 import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-default/index.css'
import VueRouter from 'vue-router'
import Axios from 'axios' Vue.use(ElementUI);
Vue.use(VueRouter);
Vue.prototype.$http = Axios import routes from './routes'
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: routes
}) import jwt from 'jsonwebtoken' router.beforeEach((to, from, next) => {
let token = localStorage.getItem('demo-token'); const decoded = token && jwt.verify(token, 'vue-koa-demo');
if (decoded) {
if (decoded.originExp - Date.now() < 0) { // 已过期
localStorage.removeItem('demo-token');
} else {
decoded.originExp = Date.now() + 60 * 60 * 1000;
token = jwt.sign(decoded, 'vue-koa-demo');
localStorage.setItem('demo-token', token);
}
} if (to.path == '/') {
if (token) {
next('/page/userHome')
}
next();
} else {
if (token) {
Vue.prototype.$http.defaults.headers.common['Authorization'] = 'Bearer ' + token; // 全局设定 header 的 token 验证
next()
} else {
next('/')
}
}
}) import Main from './components/main.vue'
const app = new Vue({
router,
render: h => h(Main)
}).$mount('#app')

其它部分,比如路由、组件,可查看之前的文章,或下载该示例在 github 上的代码

启动前端的本地环境:npm run dev

根据我在 user 表中创建的帐户与密码,登录成功

(二):商品

服务端代码与登录功能的相似,老三样:数据模型、业务控制器、api 接口:

server/models/goods.js

 const theDatabase = require('../config/db.js').theDb;
const goodsSchema = theDatabase.import('../schema/goods.js'); const getGoodsList = async (searchVal) => {
return await goodsSchema.findAndCount(
{
where: {
name: {
$like: '%' + searchVal + '%' // searchVal:要搜索的商品名称
}
}
}
);
} // 根据商品 id 查找数据
const getGoodsDetails = async (id) => {
return await goodsSchema.findById(id);
} // 添加商品
const addGoods = async (name, description, img_url) => {
await goodsSchema.create({
name,
description,
img_url
}); return true;
} // 根据商品 id 修改
const updateGoods = async (id, name, description, img_url) => {
await goodsSchema.update(
{
name,
description,
img_url
},
{
where: {
id
}
}
); return true;
} // 根据商品 id 删除数据
const removeGoods = async (id) => {
await goodsSchema.destroy({
where: {
id
}
}); return true;
} module.exports = {
getGoodsList,
getGoodsDetails,
addGoods,
updateGoods,
removeGoods
}

server/controllers/goods.js

 const goodsModel = require('../models/goods.js');

 const getGoodsList = async function() {
const data = this.request.body; // post 请求,参数在 request.body 里
const currentPage = Number(data.currentPage);
const pageSize = Number(data.pageSize);
const searchVal = data.searchVal;
const result = await goodsModel.getGoodsList(searchVal); let list = result.rows; // 根据分页输出数据
let start = pageSize * (currentPage - 1);
list = list.slice(start, start + pageSize); this.body = {
success: true,
list,
total: result.count,
msg: '获取商品列表成功!'
}
} const getGoodsDetails = async function() {
const id = this.params.id;
const list = await goodsModel.getGoodsDetails(id); this.body = {
success: true,
list: Array.isArray(list) ? list : [list],
msg: '获取商品详情成功!'
};
} const manageGoods = async function() {
const data = this.request.body;
const id = data.id;
const name = data.name;
const description = data.description;
const imgUrl = data.imgUrl; let success = false;
let msg = ''; if (id) {
if (name) {
await goodsModel.updateGoods(id, name, description, imgUrl);
success = true;
msg = '修改成功!';
}
} else if (name) {
await goodsModel.addGoods(name, description, imgUrl);
success = true;
msg = '添加成功!';
} this.body = {
success,
msg
}
} const removeGoods = async function() {
const id = this.params.id; await goodsModel.removeGoods(id); this.body = {
success: true,
msg: '删除成功!'
}
} module.exports = {
getGoodsList,
getGoodsDetails,
removeGoods,
manageGoods
}

server/routes/goods.js

const goodsController = require('../controllers/goods.js');
const router = require('koa-router')(); router.post('/goods/list', goodsController.getGoodsList);
router.get('/goods/:id', goodsController.getGoodsDetails);
router.delete('/goods/:id/', goodsController.removeGoods);
router.post('/goods/management', goodsController.manageGoods); module.exports = router;

商品列表界面:

点击“编辑”时,将 id 附在 url 上,通过 vue-router 跳转到商品详情页,根据 id 发送 ajax 获取详情数据:

(商品里有上传图片,但是无法显示,那是因为图片上传到了本地,路径也是本地的 F:\project\vue-demo\vue2.x-koa2.x\uploads\album\fc37b8a61133.jpg)

(三)图片

在 server/controllers/common 目录添加 file.js,作为所有文件上传的公共文件:

 const inspect = require('util').inspect
const path = require('path')
const fs = require('fs')
const Busboy = require('busboy') /**
* 同步创建文件目录
* @param {string} dirname 目录绝对地址
* @return {boolean} 创建目录结果
*/
function mkdirsSync( dirname ) {
if (fs.existsSync( dirname )) {
return true
} else {
if (mkdirsSync( path.dirname(dirname)) ) {
fs.mkdirSync( dirname )
return true
}
}
} /**
* 获取上传文件的后缀名
* @param {string} fileName 获取上传文件的后缀名
* @return {string} 文件后缀名
*/
function getSuffixName( fileName ) {
let nameList = fileName.split('.')
return nameList[nameList.length - 1]
} /**
* 上传文件
* @param {object} ctx koa上下文
* @param {object} options 文件上传参数
* dir 文件目录
* path 文件存放路径
* @return {promise}
*/
function uploadFile( ctx, options) {
let req = ctx.req
// let res = ctx.res
let busboy = new Busboy({headers: req.headers}) // 获取类型
let dir = options.dir || 'common'
let filePath = path.join( options.path, dir)
let mkdirResult = mkdirsSync( filePath ) return new Promise((resolve, reject) => {
console.log('文件上传中...')
let result = {
success: false,
filePath: '',
formData: {},
} // 解析请求文件事件
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
let fileName = Math.random().toString(16).substr(2) + '.' + getSuffixName(filename)
let _uploadFilePath = path.join( filePath, fileName )
let saveTo = path.join(_uploadFilePath) // 文件保存到指定路径
file.pipe(fs.createWriteStream(saveTo)) // 文件写入事件结束
file.on('end', function() {
result.success = true
result.filePath = saveTo
result.message = '文件上传成功'
console.log('文件上传成功!')
})
}) // 解析表单中其他字段信息
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
console.log('表单字段数据 [' + fieldname + ']: value: ' + inspect(val));
result.formData[fieldname] = inspect(val);
}); // 解析结束事件
busboy.on('finish', function() {
console.log('文件上传结束')
resolve(result)
}) // 解析错误事件
busboy.on('error', function(err) {
console.log('文件上传出错')
reject(result)
}) req.pipe(busboy)
})
} module.exports = {
uploadFile
}

在 server/controllers 目录添加 image.js,用来处理商品的图片上传业务:

const path = require('path');
const uploadFile = require('./common/file.js').uploadFile; const uploadImg = async function() {
const serverFilePath = path.join( __dirname, '../../uploads' ) result = await uploadFile(this, {
dir: 'album',
path: serverFilePath
}); this.body = result;
} module.exports = {
uploadImg
}

在 server/routes 目录添加 image.js,定义文件上传的接口:

const imgController = require('../controllers/image.js');
const router = require('koa-router')(); router.post('/uploads/img', imgController.uploadImg) module.exports = router;

其它更多细节请见项目

参考:全栈开发实战:用Vue2+Koa1开发完整的前后端项目

Vue2 + Koa2 实现后台管理系统的更多相关文章

  1. Vue2 后台管理系统解决方案

    基于Vue.js 2.x系列 + Element UI 的后台管理系统解决方案. github地址:https://github.com/lin-xin/manage-system demo地址:ht ...

  2. 基于vue2.0 +vuex+ element-ui后台管理系统:包括本地开发调试详细步骤

    效果演示地址, github地址: demo演示:         1.About 此项目是 vue2.0 + element-ui + node+mongodb 构建的后台管理系统,所有的数据都是从 ...

  3. 用vue2.0+vuex+vue-router+element-ui+mockjs实现后台管理系统的实践探索

    A magical vue element touzi admin. 效果演示地址 更多demo展示 分支说明 master分支:前后端统一开发的版本:可以用于学习nodejs+mongodb+exp ...

  4. xxx金融后台管理系统详细版:包括本地开发调试详细步骤

    效果演示地址, github地址: demo演示:         1.About 此项目是 vue2.0 + element-ui + node+mongodb 构建的后台管理系统,所有的数据都是从 ...

  5. vue-quasar-admin 一个包含通用权限控制的后台管理系统

    vue-quasar-admin   Quasar-Framework 是一款基于vue.js开发的开源的前端框架, 它能帮助web开发者快速创建以下网站:响应式网站,渐进式应用,手机应用(通过Cor ...

  6. vue开发后台管理系统小结

    最近工作需要用vue开发了后台管理系统,由于是第一次开发后台管理系统,中间也遇到了一些坑,想在这里做个总结,也算是对于自己工作的一个肯定.我们金融性质的网站所以就不将代码贴出来哈 一.项目概述 首先工 ...

  7. 推荐几个Laravel 后台管理系统

    小编推荐几个Laravel 后台管理系统 由百牛信息技术bainiu.ltd整理发布于博客园 一.不容错过的Laravel后台管理扩展包 —— Voyager 简介Voyager是一个你不容错过的La ...

  8. Serverless + Egg.js 后台管理系统实战

    本文将介绍如何基于 Egg.js 和 Serverless 实现一个后台管理系统 作为一名前端开发者,在选择 Nodejs 后端服务框架时,第一时间会想到 Egg.js,不得不说 Egg.js 是一个 ...

  9. Vue3 + Element ui 后台管理系统

    Vue3 + Element ui  后台管理系统 概述:这是一个用vue3.0和element搭建的后台管理系统界面. 项目git地址: https://github.com/whiskyma/vu ...

随机推荐

  1. 基于ASP.NET MVC的快速开发平台,给你的开发一个加速度!

    基于ASP.NET MVC的快速开发平台,给你的开发一个加速度! bingo炸了 2017/4/6 11:07:21 阅读(37) 评论(0) 现在的人做事情都讲究效率,最好能达到事半功倍那种效果,软 ...

  2. idea mac 控制台中文乱码

    参考:https://blog.csdn.net/lheangus/article/details/48915357 修改内容 -Dfile.encoding=UTF-8

  3. Objective-C 对象的类型与动态结合

    创建: 2018/01/21 更新: 2018/01/22 标题前增加 [Objective-C] 完成: 2018/01/24 更新: 2018/01/24 加红加粗属性方法的声明 [不直接获取内部 ...

  4. bzoj 2726: [SDOI2012]任务安排【cdq+斜率优化】

    cdq复健.jpg 首先列个n方递推,设sf是f的前缀和,st是t的前缀和: \[ f[i]=min(f[j]+s*(sf[n]-sf[j])+st[i]*(sf[i]-sf[j])) \] 然后移项 ...

  5. 【题解】PIE [POI2015] [P3585]

    [题解]\(PIE\) \([POI2015]\) \([P3585]\) 逼自己每天一道模拟题 传送门:\(PIE\) \([POI2015]\) \([P3585]\) [题目描述] 一张 \(n ...

  6. 贪心 Codeforces Round #135 (Div. 2) C. Color Stripe

    题目传送门 /* 贪心:当m == 2时,结果肯定是ABABAB或BABABA,取最小改变量:当m > 2时,当与前一个相等时, 改变一个字母 同时不和下一个相等就是最优的解法 */ #incl ...

  7. Java compiler level does not match the version of the installed Java project facet问题处理

    从SVN上下载应用后在Problems面板中提示以下错误信息: Java compiler level does not match the version of the installed Java ...

  8. Spring.Net学习笔记(5)-集合注入

    一.开发环境 系统:Win10 编译器:VS2013 .net版本:.net framework4.5 二.涉及程序集 Spring.Core.dll 1.3.1 Common.Loggin.dll ...

  9. 3122 奶牛代理商 VIII

    3122 奶牛代理商 VIII 时间限制: 3 s 空间限制: 256000 KB 题目等级 : 大师 Master       题目描述 Description 小徐是USACO中国区的奶牛代理商, ...

  10. Javascript中的那些bug

    1. x(比如document) is not defined 不止要检查是不是没有声明变量就使用了,还要检查是不是对象的方法调用写错了!比如: alert( document.getElementB ...