├── front ├── src │ ├── images │ │ ├── code.png │ │ ├── logo.png │ │ ├── user.png │ │ ├── banner.jpg │ │ ├── banner2.jpg │ │ ├── edit_i.png │ │ ├── edit_img.png │ │ ├── favicon.ico │ │ ├── icon_add.png │ │ ├── icon_qq.png │ │ ├── edit_code.png │ │ ├── edit_link.png │ │ ├── edit_title.png │ │ ├── icon_edit.png │ │ ├── icon_fail.png │ │ ├── icon_weibo.png │ │ ├── spot_arrow.png │ │ ├── edit_bolder.png │ │ ├── icon_attract.png │ │ ├── icon_delete.png │ │ ├── icon_search.png │ │ ├── icon_success.png │ │ ├── icon_wechat.png │ │ ├── shutdown_btn.png │ │ └── icon_logo_text.png │ ├── until │ │ └── scss │ │ │ ├── app.scss │ │ │ ├── common.scss │ │ │ ├── _reset.scss │ │ │ └── _mixin.scss │ ├── store │ │ ├── mutation-types.js │ │ ├── index.js │ │ └── actions.js │ ├── app.vue │ ├── main.js │ ├── dev │ │ └── timeTransform.js │ ├── components │ │ ├── reusable │ │ │ ├── popTips.vue │ │ │ ├── bottomBar.vue │ │ │ ├── cards.vue │ │ │ ├── topBar.vue │ │ │ └── login.vue │ │ ├── article.vue │ │ ├── query.vue │ │ ├── personal.vue │ │ ├── index.vue │ │ └── create.vue │ └── api │ │ └── index.js ├── build │ ├── webpack.dev.config.js │ ├── webpack.prod.config.js │ └── webpack.base.config.js ├── app.js ├── package.json └── index.html ├── server ├── public │ └── images │ │ ├── user │ │ ├── 1_01.png │ │ ├── 2_02.png │ │ ├── cd.png │ │ ├── 2days.png │ │ ├── user2.png │ │ ├── user_xiaoyu.jpg │ │ └── 偶像页(日韩。港台。欧美。内陆)灰底版_03.png │ │ └── article │ │ ├── 1_01.png │ │ ├── 1_02.png │ │ ├── 1_03.png │ │ ├── 1_07.png │ │ ├── cover1.png │ │ ├── cover2.png │ │ ├── cover3.png │ │ ├── cover4.png │ │ ├── cover5.png │ │ ├── pic1.png │ │ ├── VIP页面_28.png │ │ ├── 会员中心x_02.png │ │ ├── 官网移动端_02.jpg │ │ ├── banner_02.jpg │ │ ├── banner_bg.png │ │ ├── icon_delete.png │ │ ├── icon_edit.png │ │ ├── 这才是真正的自我(01)_01.png │ │ ├── banner_iconlist_02.png │ │ └── 偶像页(日韩。港台。欧美。内陆)灰底版_03.png ├── routes │ ├── index.js │ ├── articles.js │ └── users.js ├── schema │ ├── user.js │ └── article.js ├── config │ └── mongoose.js ├── models │ ├── user.js │ └── article.js ├── package.json ├── bin │ └── www ├── app.js └── controllers │ ├── user.js │ └── article.js └── README.md /front/src/images/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/code.png -------------------------------------------------------------------------------- /front/src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/logo.png -------------------------------------------------------------------------------- /front/src/images/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/user.png -------------------------------------------------------------------------------- /front/src/images/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/banner.jpg -------------------------------------------------------------------------------- /front/src/images/banner2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/banner2.jpg -------------------------------------------------------------------------------- /front/src/images/edit_i.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/edit_i.png -------------------------------------------------------------------------------- /front/src/images/edit_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/edit_img.png -------------------------------------------------------------------------------- /front/src/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/favicon.ico -------------------------------------------------------------------------------- /front/src/images/icon_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_add.png -------------------------------------------------------------------------------- /front/src/images/icon_qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_qq.png -------------------------------------------------------------------------------- /front/src/images/edit_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/edit_code.png -------------------------------------------------------------------------------- /front/src/images/edit_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/edit_link.png -------------------------------------------------------------------------------- /front/src/images/edit_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/edit_title.png -------------------------------------------------------------------------------- /front/src/images/icon_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_edit.png -------------------------------------------------------------------------------- /front/src/images/icon_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_fail.png -------------------------------------------------------------------------------- /front/src/images/icon_weibo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_weibo.png -------------------------------------------------------------------------------- /front/src/images/spot_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/spot_arrow.png -------------------------------------------------------------------------------- /front/src/images/edit_bolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/edit_bolder.png -------------------------------------------------------------------------------- /front/src/images/icon_attract.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_attract.png -------------------------------------------------------------------------------- /front/src/images/icon_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_delete.png -------------------------------------------------------------------------------- /front/src/images/icon_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_search.png -------------------------------------------------------------------------------- /front/src/images/icon_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_success.png -------------------------------------------------------------------------------- /front/src/images/icon_wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_wechat.png -------------------------------------------------------------------------------- /front/src/images/shutdown_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/shutdown_btn.png -------------------------------------------------------------------------------- /server/public/images/user/1_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/user/1_01.png -------------------------------------------------------------------------------- /server/public/images/user/2_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/user/2_02.png -------------------------------------------------------------------------------- /server/public/images/user/cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/user/cd.png -------------------------------------------------------------------------------- /front/src/images/icon_logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/front/src/images/icon_logo_text.png -------------------------------------------------------------------------------- /server/public/images/user/2days.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/user/2days.png -------------------------------------------------------------------------------- /server/public/images/user/user2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/user/user2.png -------------------------------------------------------------------------------- /server/public/images/article/1_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/1_01.png -------------------------------------------------------------------------------- /server/public/images/article/1_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/1_02.png -------------------------------------------------------------------------------- /server/public/images/article/1_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/1_03.png -------------------------------------------------------------------------------- /server/public/images/article/1_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/1_07.png -------------------------------------------------------------------------------- /server/public/images/article/cover1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/cover1.png -------------------------------------------------------------------------------- /server/public/images/article/cover2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/cover2.png -------------------------------------------------------------------------------- /server/public/images/article/cover3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/cover3.png -------------------------------------------------------------------------------- /server/public/images/article/cover4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/cover4.png -------------------------------------------------------------------------------- /server/public/images/article/cover5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/cover5.png -------------------------------------------------------------------------------- /server/public/images/article/pic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/pic1.png -------------------------------------------------------------------------------- /server/public/images/article/VIP页面_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/VIP页面_28.png -------------------------------------------------------------------------------- /server/public/images/article/会员中心x_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/会员中心x_02.png -------------------------------------------------------------------------------- /server/public/images/article/官网移动端_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/官网移动端_02.jpg -------------------------------------------------------------------------------- /server/public/images/user/user_xiaoyu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/user/user_xiaoyu.jpg -------------------------------------------------------------------------------- /server/public/images/article/banner_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/banner_02.jpg -------------------------------------------------------------------------------- /server/public/images/article/banner_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/banner_bg.png -------------------------------------------------------------------------------- /server/public/images/article/icon_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/icon_delete.png -------------------------------------------------------------------------------- /server/public/images/article/icon_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/icon_edit.png -------------------------------------------------------------------------------- /front/build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | var config = require('./webpack.base.config') 2 | 3 | config.devtool = '#source-map' 4 | 5 | module.exports = config 6 | -------------------------------------------------------------------------------- /server/public/images/article/这才是真正的自我(01)_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/这才是真正的自我(01)_01.png -------------------------------------------------------------------------------- /server/public/images/article/banner_iconlist_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/banner_iconlist_02.png -------------------------------------------------------------------------------- /server/public/images/user/偶像页(日韩。港台。欧美。内陆)灰底版_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/user/偶像页(日韩。港台。欧美。内陆)灰底版_03.png -------------------------------------------------------------------------------- /server/public/images/article/偶像页(日韩。港台。欧美。内陆)灰底版_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L-Rain/vue2-koa-mongoDB-/HEAD/server/public/images/article/偶像页(日韩。港台。欧美。内陆)灰底版_03.png -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')() 2 | const article = require('../controllers/article') 3 | 4 | router.get('/', article.getAllArticle) 5 | .get('/index', article.getAllArticle) 6 | 7 | module.exports = router; 8 | -------------------------------------------------------------------------------- /server/schema/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var mongoose = require('mongoose'); 3 | 4 | var userSchema = new mongoose.Schema({ 5 | userName: String, 6 | userPassword: String, 7 | userNickName: String, 8 | userImg : String 9 | }); 10 | 11 | module.exports = mongoose.model('User', userSchema); 12 | -------------------------------------------------------------------------------- /server/config/mongoose.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const mongooseDB = function () { 4 | //重点在这一句,赋值一个全局的承诺。 5 | mongoose.Promise = global.Promise 6 | mongoose.connect('mongodb://127.0.0.1:27017/wecut_blog') 7 | return mongoose.connection 8 | } 9 | 10 | module.exports = mongooseDB 11 | -------------------------------------------------------------------------------- /server/routes/articles.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')() 2 | const article = require('../controllers/article') 3 | 4 | router.post('/create', article.createArticle) 5 | .post('/edit', article.editArticle) 6 | .post('/removeAll', article.removeAll) 7 | .get('/getAll', article.getAllArticle) 8 | .get('/:id', article.getArticle) 9 | 10 | module.exports = router; 11 | -------------------------------------------------------------------------------- /server/routes/users.js: -------------------------------------------------------------------------------- 1 | const router = require('koa-router')(); 2 | const user = require('../controllers/user') 3 | 4 | 5 | console.log("匹配前缀路由") 6 | router.post('/login', user.login) 7 | .post('/register', user.register) 8 | .post('/removeAll', user.removeAll) 9 | .get('/checkOut', user.userVerify) 10 | .get('/query', user.query) 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /front/src/until/scss/app.scss: -------------------------------------------------------------------------------- 1 | /** IMPORTS **/ 2 | @import '_mixin.scss'; 3 | @import '_reset.scss'; 4 | 5 | /** GLOBALS **/ 6 | body { 7 | width: 100%; 8 | overflow-x: hidden; 9 | font-family: "Microsoft YaHei", "微软雅黑", STHeiTi, sans-serif,"Helvetica Neue",Helvetica; 10 | font-size: 16px; 11 | @include font-smoothing(); 12 | background-color: #f6f6f5; 13 | @include clearfix(); 14 | } -------------------------------------------------------------------------------- /front/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const SHOW_MSG = 'SHOW_MSG' 2 | export const SHOW_ERROR_MSG = 'SHOW_ERROR_MSG' 3 | 4 | 5 | export const LOGIN_JUDGE = 'LOGIN_JUDGE' 6 | export const USER_LOGIN = 'USER_LOGIN' 7 | export const POP_LOGIN = 'POP_LOGIN' 8 | export const POPSHOW = 'POPSHOW' 9 | export const POPTEXT = 'POPTEXT' 10 | export const GET_ARTICLES = 'GET_ARTICLES' 11 | export const GET_ARTICLE = 'GET_ARTICLE' 12 | export const GET_USER_ARTICLES = 'GET_USER_ARTICLES' 13 | 14 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const UserModel = require('../schema/user'); 4 | 5 | class User { 6 | constructor () { 7 | this.model = UserModel; 8 | } 9 | save (opts, fn) { 10 | this.entity = new UserModel(opts) 11 | return this.entity.save(opts) 12 | } 13 | query (opts) { 14 | return this.model.find(opts).exec() 15 | } 16 | queryAll () { 17 | return this.model.find({}).exec() 18 | } 19 | removeAll (fn) { 20 | return this.model.remove({}) 21 | } 22 | } 23 | 24 | module.exports = User 25 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blink-Server", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "co-busboy": "^1.0.2", 10 | "kcors": "^1.2.1", 11 | "debug": "^2.2.0", 12 | "koa": "^1.1.2", 13 | "koa-bodyparser": "^2.0.1", 14 | "koa-json": "^1.1.1", 15 | "koa-jwt": "^1.2.0", 16 | "koa-logger": "^1.3.0", 17 | "koa-onerror": "^1.2.1", 18 | "koa-router": "^5.3.0", 19 | "koa-static": "^1.5.2", 20 | "koa-views": "^3.1.0", 21 | "moment": "^2.14.1", 22 | "mongoose": "^4.6.0" 23 | } 24 | } -------------------------------------------------------------------------------- /front/app.js: -------------------------------------------------------------------------------- 1 | var koa = require('koa'); 2 | var app = koa(); 3 | 4 | // var bodyParser = require('body-parser'); 5 | // var morgan = require('morgan';) 6 | 7 | var MongoClient = require('mongodb').MongoClient; 8 | var mongoUrl = 'mongodb://localhost:4444/test'; 9 | var _db; 10 | 11 | app.use(morgan('dev')); 12 | app.use(bodyParser.json()); 13 | 14 | MongoClient.connect(mongoUrl, function(err, db){ 15 | if (err) { 16 | console.log(err); 17 | return; 18 | } 19 | console.log('connected to mongo'); 20 | _db = db; 21 | app.listen(8888, function () { 22 | console.log('server is running...'); 23 | }); 24 | }) 25 | -------------------------------------------------------------------------------- /server/schema/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var mongoose = require('mongoose'); 3 | 4 | //定义一个schema 5 | var articleSchema = new mongoose.Schema({ 6 | articleTitle: String, 7 | articleIntro: String, 8 | mainTag: String, 9 | subTags: Array, 10 | sourceArticle: String, 11 | markedArticle: String, 12 | coverImg: String, 13 | articleImgs: Array, 14 | timeStamp : String, 15 | thisUser : { 16 | _id : String, 17 | userName: String, 18 | userPassword: String, 19 | userImg : String, 20 | userNickName : String, 21 | } 22 | }); 23 | 24 | //将schema发布为model 25 | module.exports = mongoose.model('Article', articleSchema); 26 | -------------------------------------------------------------------------------- /front/build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var config = require('./webpack.base.config') 3 | 4 | config.plugins = (config.plugins || []).concat([ 5 | // this allows uglify to strip all warnings 6 | // from Vue.js source code. 7 | new webpack.DefinePlugin({ 8 | 'process.env': { 9 | NODE_ENV: '"production"' 10 | } 11 | }), 12 | // This minifies not only JavaScript, but also 13 | // the templates (with html-minifier) and CSS (with cssnano)! 14 | new webpack.optimize.UglifyJsPlugin({ 15 | compress: { 16 | warnings: false 17 | } 18 | }), 19 | new webpack.optimize.OccurenceOrderPlugin() 20 | ]) 21 | 22 | module.exports = config 23 | -------------------------------------------------------------------------------- /front/src/until/scss/common.scss: -------------------------------------------------------------------------------- 1 | @import "mixin"; 2 | 3 | //弹出层设置 4 | .pop_layer { 5 | position: fixed; 6 | display: none; 7 | z-index: 3; 8 | width: 100%; 9 | height: 100%; 10 | top: 0; 11 | left: 0; 12 | background: rgba(0,0,0,0.6); 13 | img { 14 | width: 100%; 15 | } 16 | //提示框 17 | .tips { 18 | position: absolute; 19 | @include remValue(width, 350); 20 | top: 50%; 21 | left: 50%; 22 | @include gridStyle(0.3rem); 23 | @include remValue(margin-left, -350/2); 24 | @include remValue(margin-top, -400/2); 25 | padding: 5% 0; 26 | .tips_light { 27 | @include gridRem(51, 73); 28 | margin: 5% auto; 29 | } 30 | .tips_text { 31 | font-family: "微软雅黑","Microsoft Yahei"; 32 | font-size: 0.7rem; 33 | color: #8c8c8c; 34 | text-align: center; 35 | margin-bottom: 5%; 36 | } 37 | .tips_text_h { 38 | font-size: 0.9rem; 39 | font-weight: bold; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /front/src/app.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /server/models/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | //引入定义modle,执行查询更改操作 4 | const ArticleModel = require('../schema/article'); 5 | 6 | class Article { 7 | constructor () { 8 | this.model = ArticleModel; 9 | } 10 | save (opts) { 11 | this.entity = new ArticleModel(opts); 12 | return this.entity.save(opts); 13 | } 14 | query (opts) { 15 | return this.model.find(opts) 16 | .sort({ _id: -1 }) 17 | .exec() 18 | } 19 | queryAll () { 20 | //query = this.modle.find({}),query下可执行链式操作,每个链式返回query 21 | //如果不提供回调函数,所有这些方法都返回 Query 对象,它们都可以被再次修改(比如增加选项、键等),直到调用 exec 方法。 22 | // query.exec(function (err, docs) { 23 | // // called when the `query.complete` or `query.error` are called 24 | // // internally 25 | // }); 26 | //sort为排序,-1为降序,1为升序 27 | return this.model.find({}) 28 | .sort({ _id: -1 }) 29 | .exec() 30 | } 31 | queryById (id) { 32 | console.log(id) 33 | return this.model.findById(id) 34 | } 35 | remove (id, fn) { 36 | return this.model.findById(id).then(function (doc) { 37 | if (!doc) return fn(null, false); 38 | return doc.remove(); 39 | }) 40 | } 41 | removeAll (fn) { 42 | return this.model.remove({}) 43 | } 44 | } 45 | 46 | module.exports = Article; 47 | -------------------------------------------------------------------------------- /front/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | import index from './components/index.vue' 3 | import article from './components/article.vue' 4 | import create from './components/create.vue' 5 | import personal from './components/personal.vue' 6 | import query from './components/query.vue' 7 | import store from './store' 8 | // var store = require('./store/index') 9 | import './until/scss/app.scss'; 10 | 11 | var Vue = require('vue') 12 | var VueRouter = require('vue-router') 13 | var VueResource = require('vue-resource') 14 | var App = require('./app.vue') 15 | 16 | Vue.use(VueResource); 17 | Vue.use(VueRouter); 18 | 19 | const routes = [ 20 | { 21 | path: '/', 22 | component: index 23 | }, 24 | { 25 | path: '/index', 26 | component:index 27 | }, 28 | { 29 | path: '/article', 30 | component: article 31 | }, 32 | { 33 | path: '/query', 34 | component: query 35 | }, 36 | { 37 | path: '/personal/:userId', 38 | component: personal, 39 | name : "personal", 40 | }, 41 | { 42 | path: '/article/:articleId', 43 | name : "article", 44 | component: article 45 | }, 46 | { 47 | path: '/create', 48 | component: create 49 | }, 50 | { 51 | path: '/edit/:articleId', 52 | component: create, 53 | name : "edit" 54 | } 55 | ] 56 | const router = new VueRouter({ 57 | routes 58 | }); 59 | 60 | const app = new Vue({ 61 | el: 'app', 62 | router, 63 | store, 64 | render: h => h(App) 65 | }) 66 | -------------------------------------------------------------------------------- /front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blink-Front", 3 | "version": "1.0.0", 4 | "description": "Blink-Front", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --inline --hot --host 192.168.2.63 --port 8004 --config build/webpack.dev.config.js", 8 | "build": "webpack --progress --hide-modules --config build/webpack.prod.config.js" 9 | }, 10 | "dependencies": { 11 | "github-markdown-css": "^2.4.0", 12 | "highlight.js": "^9.6.0", 13 | "marked": "^0.3.6", 14 | "vue-resource": "^1.0.3", 15 | "vue-router": "^2.1.1", 16 | "vuex": "^2.0.0", 17 | "vue": "^2.1.4" 18 | }, 19 | "devDependencies": { 20 | "koa": "^1.2.4", 21 | "babel-core": "^6.3.26", 22 | "babel-loader": "^6.1.0", 23 | "babel-plugin-transform-runtime": "^6.1.2", 24 | "babel-preset-es2015": "^6.1.2", 25 | "babel-preset-stage-0": "^6.1.2", 26 | "babel-runtime": "^5.8.0", 27 | "body-parser": "^1.15.2", 28 | "css-loader": "^0.23.0", 29 | "eslint": "^1.10.3", 30 | "eslint-loader": "^1.1.1", 31 | "extract-text-webpack-plugin": "^0.9.1", 32 | "file-loader": "^0.9.0", 33 | "mongodb": "^2.2.12", 34 | "node-sass": "^3.8.0", 35 | "sass-loader": "^4.0.0", 36 | "style-loader": "^0.13.0", 37 | "stylus-loader": "^1.4.0", 38 | "template-html-loader": "0.0.3", 39 | "url-loader": "^0.5.7", 40 | "vue-hot-reload-api": "^1.2.0", 41 | "vue-html-loader": "^1.0.0", 42 | "vue-loader": "^7.3.0", 43 | "webpack": "^1.12.2", 44 | "webpack-dev-server": "^1.12.0" 45 | } 46 | } -------------------------------------------------------------------------------- /front/src/dev/timeTransform.js: -------------------------------------------------------------------------------- 1 | function timeTransform (timespan) { 2 | if (!!!timespan) { 3 | return "历史遗留"; 4 | } 5 | timespan = parseInt(timespan)*1000; 6 | var dateTime = new Date(timespan); 7 | 8 | var year = dateTime.getFullYear(), 9 | month = dateTime.getMonth() + 1, 10 | day = dateTime.getDate(), 11 | hour = dateTime.getHours(), 12 | minute = dateTime.getMinutes(), 13 | second = dateTime.getSeconds(), 14 | now = new Date(), 15 | now_new = Date.parse(new Date()), 16 | 17 | milliseconds = 0, 18 | timeSpanStr; 19 | 20 | milliseconds = now_new - timespan; 21 | 22 | if (milliseconds <= 1000 * 60 * 1) { 23 | timeSpanStr = '刚刚'; 24 | }else if (1000 * 60 * 1 < milliseconds && milliseconds <= 1000 * 60 * 60) { 25 | timeSpanStr = Math.round((milliseconds / (1000 * 60))) + '分钟前'; 26 | }else if (1000 * 60 * 60 * 1 < milliseconds && milliseconds <= 1000 * 60 * 60 * 24) { 27 | timeSpanStr = Math.round(milliseconds / (1000 * 60 * 60)) + '小时前'; 28 | }else if (1000 * 60 * 60 * 24 < milliseconds && milliseconds <= 1000 * 60 * 60 * 24 * 15) { 29 | timeSpanStr = Math.round(milliseconds / (1000 * 60 * 60 * 24)) + '天前'; 30 | }else if (milliseconds > 1000 * 60 * 60 * 24 * 15 && year == now.getFullYear()) { 31 | timeSpanStr = month + '-' + day + ' ' + hour + ':' + minute; 32 | }else { 33 | timeSpanStr = year + '-' + month + '-' + day + ' ' + hour + ':' + minute; 34 | } 35 | return timeSpanStr; 36 | }; 37 | module.exports = timeTransform; -------------------------------------------------------------------------------- /front/src/components/reusable/popTips.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 29 | -------------------------------------------------------------------------------- /front/build/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 2 | 3 | module.exports = { 4 | entry: './src/main.js', 5 | output: { 6 | path: './dist', 7 | publicPath: '/dist/', 8 | filename: 'build.js' 9 | }, 10 | resolve: { 11 | //自动扩展文件后缀名,意味着require模块可以省略不写后缀名 12 | extensions: ['', '.js', '.vue', '.scss'], 13 | //依赖文件,重命名,打包入文件 14 | alias : { 15 | 'vue$': 'vue/dist/vue.common.js', 16 | } 17 | }, 18 | externals : { 19 | 'zepto' : 'Zepto', 20 | 'swiper' : 'Swiper', 21 | 'fastClick' : 'FastClick', 22 | }, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.vue$/, 27 | loader: 'vue' 28 | }, 29 | { 30 | test: /\.js$/, 31 | loader: 'babel?presets=es2015', 32 | exclude: /node_modules/ 33 | }, 34 | { 35 | test: /\.(png|jpg|gif)$/, 36 | loader: 'url', 37 | query: { 38 | limit: 12000, 39 | name: '[name].[ext]' 40 | } 41 | }, 42 | { test: /\.scss$/, loader: 'style!css!sass'}, 43 | { 44 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 45 | loader: "url", 46 | query: { 47 | name: '[name].[ext]?[hash]&mimetype=application/font-woff' 48 | } 49 | } 50 | ] 51 | }, 52 | vue: { 53 | loaders: { 54 | css: ExtractTextPlugin.extract( 55 | "style-loader", 56 | "css-loader?sourceMap", 57 | { 58 | publicPath: "./dist/" 59 | } 60 | ) 61 | } 62 | }, 63 | plugins: [ 64 | new ExtractTextPlugin("style.css") 65 | ], 66 | babel: { 67 | presets: ['es2015', 'stage-0'], 68 | plugins: ['transform-runtime'] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /front/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | Blink 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('demo:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3030'); 16 | // app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app.callback()); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | console.log('http://localhost:' + port) 29 | // server.listen(port); 30 | server.listen(port, '192.168.2.63'); 31 | server.on('error', onError); 32 | server.on('listening', onListening); 33 | 34 | /** 35 | * Normalize a port into a number, string, or false. 36 | */ 37 | 38 | function normalizePort(val) { 39 | var port = parseInt(val, 10); 40 | 41 | if (isNaN(port)) { 42 | // named pipe 43 | return val; 44 | } 45 | 46 | if (port >= 0) { 47 | // port number 48 | return port; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | /** 55 | * Event listener for HTTP server "error" event. 56 | */ 57 | 58 | function onError(error) { 59 | if (error.syscall !== 'listen') { 60 | throw error; 61 | } 62 | 63 | var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; 87 | debug('Listening on ' + bind); 88 | } 89 | -------------------------------------------------------------------------------- /front/src/api/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueResource from 'vue-resource' 3 | import store from '../store' 4 | 5 | Vue.use(VueResource) 6 | const API_ROOT = 'http://192.168.2.63:3030/' 7 | const indexResource = Vue.resource(API_ROOT + 'index{/id}') 8 | const usersGetResource = Vue.resource(API_ROOT + 'users{/id}'); 9 | //formdata上传数据必须搭上emulateJSON参数 10 | const usersResource = Vue.resource(API_ROOT + 'users{/id}', { emulateJSON: true }); 11 | const articlePostResource = Vue.resource(API_ROOT + 'article{/id}', { emulateJSON: true }); 12 | const articleGetResource = Vue.resource(API_ROOT + 'article{/id}'); 13 | 14 | //vue-resource拦截器 15 | Vue.http.interceptors.push((request, next) => { 16 | Vue.http.headers.common['token'] = sessionStorage.token || ''; 17 | next((response) => { 18 | //在响应之后传给then之前对response进行修改和逻辑判断 19 | if (response.status == 401) { 20 | console.log("401 : " + JSON.stringify(response.body)) 21 | store.state.loginJudge = false; 22 | sessionStorage.removeItem('token'); 23 | //文章创建token判断弹窗提示 24 | if (response.url.indexOf('article') > -1) { 25 | store.commit('SHOW_MSG', {state : false, msg: "请重新登录"}) 26 | } 27 | } 28 | }) 29 | }) 30 | 31 | export default { 32 | getArticleById (id) { 33 | return articleGetResource.get({id : id}) 34 | }, 35 | getArticlesByUser (id) { 36 | return articleGetResource.get({id: 'user', 'userId': id}) 37 | }, 38 | getALLArticle (opts) { 39 | return articleGetResource.get({id : 'getAll'}) 40 | }, 41 | getUserLogin (opts){ 42 | return usersGetResource.get({id: 'checkOut'}) 43 | }, 44 | userLogin (opts) { 45 | return usersResource.save({id: 'login'}, opts) 46 | }, 47 | userRegister (opts) { 48 | return usersResource.save({id: 'register'},opts) 49 | }, 50 | createArticle (opts) { 51 | return articlePostResource.save({id: 'create'},opts) 52 | }, 53 | editArticle (opts) { 54 | return articlePostResource.save({id: 'edit'},opts) 55 | }, 56 | deleteArticle (id) { 57 | return articleGetResource.get({id: 'delete', 'deleteId': id}) 58 | }, 59 | deleteAll (opts){ 60 | // return articleGetResource.save({id : "removeAll"}, opts) 61 | return usersGetResource.save({id : "removeAll"}, opts) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | var app = require('koa')() 2 | , koa = require('koa-router')() 3 | , logger = require('koa-logger') 4 | , json = require('koa-json') 5 | , views = require('koa-views') 6 | , jwt = require('koa-jwt') 7 | , cors = require('kcors') 8 | , static = require('koa-static') 9 | , onerror = require('koa-onerror'); 10 | 11 | var users = require('./routes/users'); 12 | var article = require('./routes/articles'); 13 | 14 | var db = require('./config/mongoose')(); 15 | db.on('error', console.error.bind(console, 'error: connect error!')) 16 | db.once('open', function () { 17 | // 一次打开记录 18 | console.log('connect success!') 19 | }) 20 | 21 | // global middlewares 22 | app.use(require('koa-bodyparser')()); 23 | app.use(json()); 24 | app.use(logger()); 25 | //cors解决跨域问题 26 | app.use(cors()); 27 | //static配置静态文件,可直接访问该文件夹下的文件 28 | app.use(static(__dirname + '/public/')); 29 | app.use(jwt({ 30 | secret: 'Blink_1.0', 31 | passthrough: true 32 | })); 33 | 34 | 35 | //token验证 36 | app.use(function *(next){ 37 | var ctx = this, 38 | thisUrl = ctx.request.url; 39 | console.log(thisUrl) 40 | // 如果来源路径不是create或checkOut,直接跳过验证 41 | if (thisUrl.indexOf('create') === -1 && thisUrl.indexOf('checkOut') == -1) { 42 | return yield next; 43 | } 44 | var token = ctx.request.headers.token || ''; 45 | if (token) { 46 | var profile = jwt.verify(token, 'Blink_1.0'); 47 | if (profile) { 48 | // 设置过期时间为1天 49 | if (Date.now() - profile.original_iat < 12 * 60 * 60 * 1000) { 50 | yield next; 51 | } else { 52 | ctx.status = 401; 53 | ctx.body = { 54 | state: false, 55 | msg: 'token已过期' 56 | }; 57 | } 58 | } else { 59 | ctx.status = 401; 60 | ctx.body = { 61 | state: false, 62 | msg: 'token认证失败' 63 | } 64 | } 65 | } else { 66 | ctx.status = 401; 67 | ctx.body = { 68 | state: false, 69 | msg: 'token认证失败' 70 | } 71 | } 72 | }); 73 | 74 | 75 | app.use(function *(next){ 76 | var start = new Date; 77 | yield next; 78 | var ms = new Date - start; 79 | console.log('%s %s - %s', this.method, this.url, ms); 80 | }); 81 | 82 | 83 | //注册路由,router.routes()返回处理匹配到了请求对应的路由的router中间件 84 | //router.use([path], middleware, [...]) Use given middleware(s) before route callback. 85 | //router.use(),匹配到设置的接口地址后,进入回调中间件,再中间件里设置路由路径匹配对应后的回调函数 86 | koa.use('/users', users.routes(), users.allowedMethods()); 87 | koa.use('/article', article.routes(), article.allowedMethods()); 88 | 89 | app.use(koa.routes()); 90 | 91 | app.on('error', function(err, ctx){ 92 | logger.error('server error', err, ctx); 93 | }); 94 | 95 | module.exports = app; 96 | -------------------------------------------------------------------------------- /front/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import actions from './actions' 4 | import userHead from '../images/logo.png' 5 | import { 6 | SHOW_MSG, 7 | LOGIN_JUDGE, 8 | POPSHOW, 9 | POPTEXT, 10 | POP_LOGIN, 11 | USER_LOGIN, 12 | GET_ARTICLES, 13 | GET_ARTICLE, 14 | GET_USER_ARTICLES 15 | } from './mutation-types' 16 | 17 | Vue.use(Vuex) 18 | 19 | const state = { 20 | //登录判断 21 | loginJudge : false, 22 | popLogin : false, 23 | popComment : { 24 | popText : "", 25 | popIcon : true, 26 | popShow : false, 27 | }, 28 | //登录用户数据存储 29 | user : {}, 30 | //文章列表获取存储 31 | articles : [{ 32 | articleTitle: "--", 33 | articleIntro: "", 34 | mainTag: "", 35 | subTags: [], 36 | sourceArticle: "--", 37 | markedArticle: "--", 38 | coverImg: "", 39 | timeStamp : "", 40 | articleImgs: [], 41 | thisUser : { 42 | _id : "", 43 | userName : "", 44 | userNickName : "", 45 | userImg : userHead, 46 | } 47 | }], 48 | //单个文章依据ID索引存储 49 | article : { 50 | articleTitle: "", 51 | articleIntro: "", 52 | mainTag: "前端开发", 53 | subTags: ["Node.js"], 54 | sourceArticle: "", 55 | markedArticle: "", 56 | coverImg: "", 57 | timeStamp : "", 58 | articleImgs: [], 59 | thisUser : { 60 | _id : "", 61 | userName : "", 62 | userNickName : "", 63 | userImg : userHead, 64 | } 65 | }, 66 | //文章列表依据用户ID索引存储 67 | userArticles : [{ 68 | articleTitle: "", 69 | articleIntro: "", 70 | mainTag: "", 71 | subTags: [], 72 | sourceArticle: "", 73 | markedArticle: "", 74 | coverImg: "", 75 | timeStamp : "", 76 | articleImgs: [], 77 | thisUser : { 78 | _id : "", 79 | userName : "", 80 | userNickName : "", 81 | userImg : userHead, 82 | } 83 | }], 84 | token : "", 85 | } 86 | 87 | const mutations = { 88 | //登录状态 89 | [LOGIN_JUDGE]( state, msg ) { 90 | state.loginJudge = msg; 91 | }, 92 | //登录状态,token获取 93 | [USER_LOGIN]( state, opts ) { 94 | console.log(opts); 95 | state.user = opts.data; 96 | state.loginJudge = opts.state; 97 | }, 98 | //登录注册控件显示 99 | [POP_LOGIN]( state, msg ) { 100 | state.popLogin = msg; 101 | }, 102 | //弹窗提示层 103 | [SHOW_MSG] (state, opts) { 104 | state.popComment = { 105 | popShow : true, 106 | popIcon : opts.state, 107 | popText : opts.msg, 108 | }; 109 | }, 110 | //获取多选文章 111 | [GET_ARTICLES] (state, data) { 112 | state.articles = data; 113 | }, 114 | //获取单个文章 115 | [GET_ARTICLE] (state, data) { 116 | state.article = data[0]; 117 | }, 118 | //获取用户文章 119 | [GET_USER_ARTICLES] (state, data) { 120 | state.userArticles = data; 121 | }, 122 | } 123 | 124 | export default new Vuex.Store({ 125 | state, 126 | mutations, 127 | actions 128 | }) 129 | -------------------------------------------------------------------------------- /front/src/until/scss/_reset.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* 3 | * reset stylesheet 4 | * lisarao@gamil.com 5 | * 6 | */ 7 | 8 | // 全局 reset 定义 9 | @mixin global-reset { 10 | html, body, div, span,object, iframe, 11 | h1, h2, h3, h4, h5, h6, p, 12 | a, address, big, cite, code, 13 | em, img, q, s, 14 | small, strong, sub, sup, tt, 15 | b, u, i, center, 16 | dl, dt, dd, ol, ul, li, 17 | fieldset, form, label, legend, 18 | table, caption, tbody, tfoot, thead, tr, th, td, 19 | article, aside, canvas, details, embed, 20 | figure, figcaption, footer, header, hgroup, 21 | menu, nav, output,section, summary, 22 | time, mark, audio, video { 23 | @include reset-box-model; 24 | @include text-size-adjust; 25 | } 26 | @include reset-html5; 27 | 28 | ol,ul,li { 29 | @include reset-list-style; 30 | } 31 | table { 32 | @include reset-table; 33 | } 34 | img { 35 | @include reset-image-anchor-border; 36 | } 37 | a img { 38 | @include touch-callout; 39 | } 40 | a { 41 | @include no-underline; 42 | } 43 | #{headings(all)} { 44 | @include font-weight; 45 | } 46 | } 47 | // 盒模式 reset 定义 48 | @mixin reset-box-model { 49 | margin:0; 50 | padding:0; 51 | } 52 | // list style 53 | @mixin reset-list-style { 54 | list-style: none; 55 | } 56 | // table 57 | @mixin reset-table { 58 | border-collapse: collapse; 59 | border-spacing: 0; 60 | } 61 | //image border [ ie7- ] 62 | @mixin reset-image-anchor-border { 63 | border:0 none; 64 | } 65 | // html 5 block 66 | @mixin reset-html5 { 67 | #{elements-of-type(html5-block)}{ 68 | display:block; 69 | } 70 | } 71 | 72 | @mixin no-underline { 73 | text-decoration: none; 74 | } 75 | 76 | /* 禁止长按链接与图片弹出菜单 */ 77 | @mixin touch-callout { 78 | -webkit-touch-callout : none; 79 | } 80 | 81 | // focus outline 82 | @mixin reset-focus { 83 | outline: 0 ; 84 | } 85 | 86 | // font-size 87 | @mixin text-size-adjust { 88 | -webkit-text-size-adjust:none; 89 | -ms-text-size-adjust:100%; 90 | -webkit-text-size-adjust:100%; 91 | } 92 | 93 | // overflow -x scroll hidden 94 | @mixin set-scroll-x { 95 | overflow-x:hidden; 96 | } 97 | //用户选择文本 98 | @mixin user-select { 99 | -webkit-user-select : none; 100 | user-select : none; 101 | } 102 | //font 103 | @mixin default-font { 104 | font:1em/1.8; 105 | } 106 | //字体平滑 107 | @mixin font-smoothing { 108 | -webkit-font-smoothing:antialiased; //最佳平滑效果 109 | } 110 | //font-weight 400 111 | 112 | @mixin font-weight { 113 | font-weight:400; 114 | } 115 | 116 | //font-style normal 117 | @mixin normal-font { 118 | font-style:normal; 119 | } 120 | 121 | @mixin clearfix { 122 | .fl { 123 | float:left; 124 | display:inline; 125 | } 126 | .fr { 127 | float:right; 128 | display:inline; 129 | } 130 | .clearfix{ 131 | zoom:1; 132 | &:after{ 133 | content:''; 134 | display:block; 135 | height:0; 136 | clear:both; 137 | visibility:hidden; 138 | } 139 | } 140 | } 141 | 142 | @include global-reset; 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /front/src/components/reusable/bottomBar.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /server/controllers/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const jwt = require('koa-jwt') 3 | const User = require('../models/user') 4 | const user = new User() 5 | 6 | //上传控件 7 | var parse = require('co-busboy'); 8 | var fs = require('fs'); 9 | var os = require('os'); 10 | var path = require('path'); 11 | 12 | //登录控件 13 | const login = function *(next) { 14 | const ctx = this; 15 | const opts = ctx.request.body; 16 | yield user.query(opts).then(function (doc) { 17 | const user = doc[0] || '' 18 | if (user) { 19 | //签发token 20 | const token = jwt.sign(JSON.stringify({ userId: user._id, original_iat: Date.now()}), 'Blink_1.0'); 21 | ctx.body = { 22 | msg: '登录成功', 23 | data: { 24 | _id : user._id, 25 | userName : user.userName, 26 | userNickName : user.userNickName, 27 | userImg : user.userImg, 28 | }, 29 | state: true, 30 | token: token 31 | } 32 | } else { 33 | ctx.body = { 34 | msg: '用户名或密码错误', 35 | data: user, 36 | state: false 37 | } 38 | } 39 | }) 40 | } 41 | 42 | //注册,添加用户头像 43 | const register = function *(next) { 44 | const ctx = this; 45 | var parts = parse(this.request) 46 | var part; 47 | //此数组用于存储图片的url 48 | var fileNames = [], 49 | opts = { }; 50 | while (part = yield parts){ 51 | //此时part为返回的流对象 52 | //循环输出流对流,分别获取formdata中的是三个input值 53 | if (part.filename) { 54 | var filename = part.filename; 55 | fileNames.push(filename) 56 | var homeDir = path.resolve(__dirname, '..') 57 | var newpath = homeDir + '/public/images/user/'+ filename; 58 | //生成存储路径,要注意这里的newpath必须是绝对路径,否则Stream报错 59 | var stream = fs.createWriteStream(newpath); 60 | //写入文件流 61 | part.pipe(stream); 62 | }else if(part.length) { 63 | opts[part[0]] = part[1]; 64 | } 65 | } 66 | if(fileNames.length > 0){ 67 | opts.userImg = 'http://192.168.2.63:3030/images/user/' + fileNames[0]; 68 | } 69 | yield user.query({userName: opts.userName}).then(function (doc) { 70 | if (doc.length > 0) { 71 | ctx.body = { 72 | msg: '该账户已存在', 73 | state: false 74 | } 75 | } else { 76 | return user.save(opts) 77 | } 78 | }).then(function (doc) { 79 | if (doc) { 80 | const token = jwt.sign(JSON.stringify({ userId: doc._id, original_iat: Date.now()}), 'Blink_1.0'); 81 | ctx.body = { 82 | msg: '注册成功', 83 | data: { 84 | _id : user._id, 85 | userName : user.userName, 86 | userNickName : user.userNickName, 87 | userImg : user.userImg, 88 | }, 89 | state: true, 90 | token : token 91 | } 92 | } 93 | }) 94 | } 95 | 96 | //token验证 97 | const userVerify = function *(next) { 98 | const ctx = this; 99 | var token = ctx.request.headers.token; 100 | var profile = jwt.verify(token, 'Blink_1.0'); 101 | yield user.query({_id: profile.userId}).then(function (doc) { 102 | ctx.body = { 103 | msg: 'token验证成功', 104 | data: doc[0], 105 | state: true 106 | } 107 | }) 108 | } 109 | 110 | const query = function *(next) { 111 | const ctx = this; 112 | yield user.queryAll().then(function (doc) { 113 | ctx.body = doc 114 | }) 115 | } 116 | 117 | const removeAll = function*(next){ 118 | console.log("userRemoveAll"); 119 | yield user.removeAll().then(function (doc) { 120 | ctx.body = { 121 | msg: '删除成功', 122 | state: true 123 | } 124 | }).catch(function (error) { 125 | ctx.body = { 126 | msg: '删除失败', 127 | state: false 128 | } 129 | }) 130 | } 131 | 132 | module.exports = { 133 | login, 134 | register, 135 | query, 136 | userVerify, 137 | removeAll 138 | } 139 | -------------------------------------------------------------------------------- /front/src/components/reusable/cards.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 43 | -------------------------------------------------------------------------------- /front/src/until/scss/_mixin.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | //网站背景 4 | $bgc: #f2f5f9; 5 | //主色 6 | $gre : #3dc932; 7 | //字体色重 8 | $strong : #666; 9 | $weak : #aaa; 10 | .pr {position: relative;} 11 | .pa {position: absolute;} 12 | .pf {position: fixed;} 13 | .green {color: $gre}; 14 | 15 | //css3 tarnsition统一设置 16 | @mixin css3($time: 0.3s, $speedStyle: ease-out, $shadow: box-shadow) { 17 | transition: $shadow $time $speedStyle; 18 | -webkit-transition: $shadow $time $speedStyle; 19 | -moz-transition: $shadow $time $speedStyle; 20 | -ms-transition: $shadow $time $speedStyle; 21 | -o-transition: $shadow $time $speedStyle; 22 | } 23 | //卡片背景与圆角设置 24 | @mixin gridStyle($br:2px, $backC: #fff) { 25 | background: $backC; 26 | border-radius: $br; 27 | } 28 | //单行文本溢出设置 29 | @mixin textOverflow() { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | white-space: nowrap; 33 | } 34 | //多行文本溢出设置 35 | @mixin textOverflowP($pCount: 6) { 36 | display: -webkit-box !important; 37 | overflow: hidden; 38 | text-overflow: ellipsis; 39 | word-break: break-all; 40 | -webkit-box-orient: vertical; 41 | -webkit-line-clamp: $pCount; 42 | } 43 | //单行字体居中 44 | @mixin fontMiddle($h){ 45 | @include remValue(height, $h); 46 | @include remValue(line-height, $h); 47 | } 48 | //矩阵按钮 49 | @mixin gridButton($w, $h, $br:4px, $backC:#fff){ 50 | @include gridStyle($br, $backC); 51 | @include gridRem($w, $h); 52 | @include remValue(line-height, $h); 53 | text-align: center; 54 | } 55 | //雪碧图处理 56 | @mixin sprite($icon-name, $isRetina:false, $repeat:no-repeat) { 57 | $sprites: sprite-map("sprite/*.png"); 58 | background-repeat: $repeat; 59 | background-image: sprite-url($sprites); 60 | background-position: sprite-position($sprites, $icon-name); 61 | width: image-width(sprite-file($sprites, $icon-name)); 62 | height: image-height(sprite-file($sprites, $icon-name)); 63 | @if $isRetina { 64 | $retina: sprite-map('sprite-retina/*.png'); 65 | @media (min--moz-device-pixel-ratio: 1.3), 66 | (-o-min-device-pixel-ratio: 2.6/2), 67 | (-webkit-min-device-pixel-ratio: 1.3), 68 | (min-device-pixel-ratio: 1.3), 69 | (min-resolution: 1.3dppx) { 70 | background-image: sprite-url($retina); 71 | background-position: 0 round(nth(sprite-position($retina, $icon-name), 2) / 2); 72 | height: round(image-height(sprite-file($retina, $icon-name)) / 2); 73 | width: round(image-width(sprite-file($retina, $icon-name)) / 2); 74 | $double-width: ceil(image-width(sprite-path($retina)) / 2); 75 | $auto-height: auto; 76 | background-size: $double-width $auto-height; 77 | } 78 | } 79 | } 80 | 81 | //rem设置 82 | $browser-default-font-size: 20px !default; //设置rem根字体默认数值20 83 | //去除单位 84 | @function strip-units($number){ 85 | @return $number / ($number * 0 + 1); 86 | } 87 | //属性对应rem值设置 88 | @mixin remValue($property, $values...) { 89 | $max: length($values);//返回$values列表的长度值 90 | $pxValues: ''; 91 | $remValues: ''; 92 | 93 | @for $i from 1 through $max { 94 | $value: strip-units(nth($values, $i));//返回$values列表中的第$i个值,并将单位值去掉 95 | $browser-default-font-size: strip-units($browser-default-font-size); 96 | $pxValues: #{$pxValues + $value / $browser-default-font-size / 2}rem; //rem计算,切图数值除以2再除以根字体 97 | 98 | @if $i < $max { 99 | $pxValues: #{$pxValues + " "}; 100 | } 101 | } 102 | 103 | @for $i from 1 through $max { 104 | $value: strip-units(nth($values, $i)); 105 | $remValues: #{$remValues + $value}px; 106 | 107 | @if $i < $max { 108 | $remValues: #{$remValues + " "}; 109 | } 110 | } 111 | 112 | // #{$property}: $remValues; 113 | #{$property}: $pxValues; 114 | } 115 | 116 | //依据切图尺寸/2后定义div宽高,并返回rem值 117 | @mixin gridRem($width, $height){ 118 | $browser-default-font-size : strip-units($browser-default-font-size); 119 | // width: #{$width / 2}px; 120 | width: #{$width / 2 / $browser-default-font-size}rem; 121 | // height: #{$height / 2}px; 122 | height: #{$height / 2 / $browser-default-font-size}rem; 123 | } 124 | 125 | 126 | // @function remReturn($value){ 127 | // @return #{$value / strip-units($browser-default-font-size)}rem; 128 | // } -------------------------------------------------------------------------------- /front/src/components/article.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 114 | -------------------------------------------------------------------------------- /front/src/store/actions.js: -------------------------------------------------------------------------------- 1 | 2 | import api from '../api' 3 | import * as types from './mutation-types' 4 | 5 | export default { 6 | getUserLogin ( {commit}, opts){ 7 | opts = opts || ""; 8 | return new Promise((resolve, reject) => { 9 | api.getUserLogin(opts).then(function (res) { 10 | if (res.body.state) { 11 | commit(types.USER_LOGIN, res.body) 12 | commit(types.LOGIN_JUDGE, true) 13 | resolve(); 14 | }else { 15 | commit(types.LOGIN_JUDGE, false) 16 | reject(res.body.msg) 17 | } 18 | }).catch(function(e){ 19 | commit(types.LOGIN_JUDGE, false) 20 | reject(e.body.msg) 21 | }) 22 | }) 23 | }, 24 | userLogin ({ commit }, opts) { 25 | api.userLogin(opts).then(function (res) { 26 | commit(types.SHOW_MSG, res.body) 27 | if (res.body.state) { 28 | // cookies.set('token', res.body.token, { expires: 7 }) 29 | //登录状态更新 30 | commit(types.LOGIN_JUDGE, true) 31 | //登录控件显示关闭 32 | commit(types.POP_LOGIN, false) 33 | //用户登录信息更新 34 | commit(types.USER_LOGIN, res.body) 35 | //存储token 36 | sessionStorage.token = res.body.token; 37 | } else { 38 | commit(types.LOGIN_JUDGE, false) 39 | commit(types.POP_LOGIN, true) 40 | } 41 | }) 42 | }, 43 | userRegister ({commit}, opts) { 44 | api.userRegister(opts).then(function (res) { 45 | commit(types.SHOW_MSG, res.body) 46 | if (res.body.state) { 47 | commit(types.LOGIN_JUDGE, true) 48 | commit(types.POP_LOGIN, false) 49 | commit(types.USER_LOGIN, res.body) 50 | //存储token 51 | sessionStorage.token = res.body.token; 52 | } else { 53 | commit(types.LOGIN_JUDGE, false) 54 | commit(types.POP_LOGIN, true) 55 | } 56 | 57 | }).catch(function(e){ 58 | console.log(e); 59 | }) 60 | }, 61 | createArticle ({commit}, opts) { 62 | api.createArticle(opts).then(function (res) { 63 | commit(types.SHOW_MSG, res.body) 64 | setTimeout(function(){ 65 | location.href = "/#/index"; 66 | },800) 67 | }).catch(function(e){ 68 | console.log(e); 69 | }) 70 | }, 71 | editArticle ({commit}, opts) { 72 | api.editArticle(opts).then(function (res) { 73 | commit(types.SHOW_MSG, res.body) 74 | }).then(function (){ 75 | setTimeout(function(){ 76 | location.href = "/#/index"; 77 | },800) 78 | }).catch(function(e){ 79 | console.log(e); 80 | }) 81 | }, 82 | deleteArticle ({commit}, id) { 83 | return new Promise((resolve, reject) => { 84 | api.deleteArticle(id).then(function (res) { 85 | commit(types.SHOW_MSG, res.body) 86 | resolve(); 87 | }).catch(function(e){ 88 | resolve(e); 89 | }) 90 | }) 91 | }, 92 | getALLArticle({ commit }, opts) { 93 | api.getALLArticle(opts).then(function (res) { 94 | commit(types.GET_ARTICLES, res.body.articles) 95 | }) 96 | }, 97 | getArticleById({ commit }, id) { 98 | return new Promise((resolve, reject) => { 99 | api.getArticleById(id).then(function (res) { 100 | if (res.body.state) { 101 | commit(types.GET_ARTICLE, res.body.article) 102 | resolve(); 103 | }else { 104 | reject(res) 105 | } 106 | }).catch(function(e){ 107 | reject(e.body.msg) 108 | }) 109 | }) 110 | }, 111 | getArticlesByUser({ commit }, id) { 112 | api.getArticlesByUser(id).then(function (res) { 113 | if (res.body.state) { 114 | if (res.body.article.length !== 0) { 115 | commit(types.GET_USER_ARTICLES, res.body.article) 116 | }else { 117 | commit(types.GET_USER_ARTICLES, [{ 118 | articleTitle: "", 119 | articleIntro: "", 120 | mainTag: "", 121 | subTags: [], 122 | sourceArticle: "", 123 | markedArticle: "", 124 | coverImg: "", 125 | timeStamp : "", 126 | articleImgs: [], 127 | thisUser : { 128 | _id : "", 129 | userName : "", 130 | userNickName : "", 131 | userImg : "", 132 | } 133 | }] 134 | ) 135 | } 136 | }else { 137 | commit(types.SHOW_MSG, res.body) 138 | setTimeout(function(){ 139 | location.href = "/#/index"; 140 | },800) 141 | } 142 | }) 143 | }, 144 | deleteAll({commit}, opts){ 145 | api.deleteAll().then(function (res) { 146 | console.log("done"); 147 | }) 148 | } 149 | }; 150 | 151 | 152 | -------------------------------------------------------------------------------- /server/controllers/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Article = require('../models/article') 4 | const article = new Article() 5 | 6 | //上传控件 7 | var parse = require('co-busboy'); 8 | var fs = require('fs'); 9 | var os = require('os'); 10 | var path = require('path'); 11 | 12 | //创建文本 13 | const createArticle = function*(next) { 14 | const ctx = this; 15 | var parts = parse(this.request) 16 | var part; 17 | //此数组用于存储图片的url 18 | var fileNames = [], 19 | subTags = [], 20 | articleImgs = [], 21 | opts = {}; 22 | while (part = yield parts){ 23 | //一定要对流进行处理,否则while循环会被卡住 24 | //input上传文件就是个对象,包含了filename键值 25 | if (part.filename) { 26 | var filename = part.filename; 27 | fileNames.push(filename) 28 | var homeDir = path.resolve(__dirname, '..') 29 | var newpath = homeDir + '/public/images/article/'+ filename; 30 | //生成存储路径,要注意这里的newpath必须是绝对路径,否则Stream报错 31 | var stream = fs.createWriteStream(newpath); 32 | //写入文件流 33 | part.pipe(stream); 34 | 35 | //不是input上传文件则为数组['键','值']形式 36 | }else if(part.length) { 37 | if (part[0].indexOf("subTags") > -1) { 38 | subTags.push(part[1]); 39 | opts["subTags"] = subTags; 40 | }else { 41 | opts[part[0]] = part[1]; 42 | } 43 | } 44 | }; 45 | var fileLen = fileNames.length; 46 | if(fileLen > 0){ 47 | //设定第一个即为封面图,其他的均为文本图像 48 | opts["coverImg"] = 'http://192.168.2.63:3030/images/article/' + fileNames[0]; 49 | for (var i = 1; i < fileLen; i++) { 50 | articleImgs.push('http://192.168.2.63:3030/images/article/' + fileNames[i]); 51 | } 52 | opts["articleImgs"] = articleImgs; 53 | } 54 | opts.thisUser = JSON.parse(opts.thisUser); 55 | yield article.save(opts).then(function (doc) { 56 | ctx.body = { 57 | msg: '发布成功', 58 | data: doc, 59 | state: true 60 | } 61 | }).catch(function (error) { 62 | ctx.body = { 63 | msg: '发布失败', 64 | error: error, 65 | state: false 66 | } 67 | }) 68 | } 69 | //修改文本 70 | const editArticle = function*(next) { 71 | const ctx = this; 72 | var parts = parse(this.request) 73 | var part; 74 | //此数组用于存储图片的url 75 | var fileNames = [], 76 | subTags = [], 77 | articleImgs = [], 78 | opts = {}; 79 | while (part = yield parts){ 80 | if(part.length) { 81 | if (part[0].indexOf("subTags") > -1) { 82 | subTags.push(part[1]); 83 | opts["subTags"] = subTags; 84 | }else if(part[0].indexOf("textPic") > -1){ 85 | articleImgs.push(part[1]); 86 | opts["articleImgs"] = articleImgs; 87 | }else { 88 | opts[part[0]] = part[1]; 89 | opts["articleImgs"] = ""; 90 | } 91 | } 92 | }; 93 | opts.thisUser = JSON.parse(opts.thisUser); 94 | console.log(JSON.stringify(opts)); 95 | yield article.remove(opts.thisId).then(function(){ 96 | return article.save(opts); 97 | }).then(function (doc) { 98 | ctx.body = { 99 | msg: '修改成功', 100 | data: doc, 101 | state: true 102 | } 103 | }).catch(function (error) { 104 | ctx.body = { 105 | msg: '修改失败', 106 | error: error, 107 | state: false 108 | } 109 | }) 110 | } 111 | 112 | //获取全部文章 113 | const getAllArticle = function*(next) { 114 | const ctx = this; 115 | let results = []; 116 | yield article.query({}).then(function (doc) { 117 | for (let i = 0; i < doc.length; i++) { 118 | results.push(doc[i]) 119 | } 120 | ctx.body = { 121 | message: '获取成功', 122 | articles: results 123 | } 124 | }).catch(function (error) { 125 | ctx.body = { 126 | msg: '获取失败', 127 | error: error, 128 | state: false 129 | } 130 | }) 131 | } 132 | 133 | //处理单个文章 134 | const getArticle = function*(next) { 135 | const ctx = this; 136 | let hrefs = this.request.href, 137 | hrefsLoc = hrefs.indexOf('article/') + 8, 138 | queryByUser = false, 139 | deleteByUser = false; 140 | if (hrefs.indexOf("?userId=") > -1) { 141 | queryByUser = true; 142 | hrefsLoc = hrefs.indexOf("?userId=") + 8; 143 | }else if(hrefs.indexOf("?deleteId=") > -1){ 144 | deleteByUser = true; 145 | hrefsLoc = hrefs.indexOf("?deleteId=") + 10; 146 | } 147 | let searchId = hrefs.substr(hrefsLoc, hrefs.length); 148 | //根据用户id索引 149 | if (queryByUser) { 150 | yield article.query({'thisUser._id' : searchId}).then(function (doc) { 151 | // if (doc[0]) { 152 | ctx.body = { 153 | msg: '获取成功', 154 | article: doc, 155 | state : true, 156 | } 157 | // }else { 158 | // ctx.body = { 159 | // msg: '非法的id请求', 160 | // state : false, 161 | // } 162 | // } 163 | }).catch(function (error) { 164 | ctx.body = { 165 | msg: '获取失败', 166 | error: error, 167 | state: false 168 | } 169 | }) 170 | }else if(deleteByUser) { 171 | //删除文章 172 | yield article.remove(searchId).then(function (doc) { 173 | ctx.body = { 174 | msg: '删除成功', 175 | state: true 176 | } 177 | }).catch(function (error) { 178 | ctx.body = { 179 | msg: '删除失败', 180 | state: false 181 | } 182 | }) 183 | }else { 184 | //根据文章id索引 185 | yield article.query({_id : searchId}).then(function (doc) { 186 | if (doc[0]) { 187 | ctx.body = { 188 | msg: '获取成功', 189 | article: doc, 190 | state : true, 191 | } 192 | }else { 193 | ctx.body = { 194 | msg: '非法的id请求', 195 | state : false, 196 | } 197 | } 198 | }).catch(function (error) { 199 | ctx.body = { 200 | msg: '获取失败', 201 | error: error, 202 | state: false 203 | } 204 | }) 205 | } 206 | } 207 | 208 | const removeAll = function*(next){ 209 | console.log("removeAll"); 210 | yield article.removeAll().then(function (doc) { 211 | ctx.body = { 212 | msg: '删除成功', 213 | state: true 214 | } 215 | }).catch(function (error) { 216 | ctx.body = { 217 | msg: '删除失败', 218 | state: false 219 | } 220 | }) 221 | } 222 | 223 | module.exports = { 224 | getArticle, 225 | getAllArticle, 226 | createArticle, 227 | editArticle, 228 | removeAll 229 | } 230 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 从设计到前端重构后端交互数据库存储一手操作,新手全栈入门 4 | 5 | 6 | (请先创建用户发布一篇文章以供数据测试) 7 | # Blink前后端 8 | 9 | 运行方式:(一定要安装mongodb) mongodb安装教程: https://segmentfault.com/a/1190000002547229 10 |
 11 | //启动mongo服务
 12 | cd: /mongo
 13 | ./mongod
 14 | 
 15 | //安装前端依赖,启动服务
 16 | cd: /fornt
 17 | npm install all
 18 | npm run dev
 19 | 
 20 | //安装后端依赖,启动服务
 21 | cd: /server
 22 | npm install all
 23 | npm run dev
 24 | 
25 | 26 | ## 跳过设计流程(这里把自己内部分享直接复制来了) 27 | ## “闪现”制作流程之(二):再讲前端 28 |

前端用的就是vue渲染,当然还有webpac,vuex,vue-router,vue-resource等冚家大礼包。只不过这次全面升级,变成冚家2.0

29 |

记录点自己遇到的坑吧:
1,vue-router2.0的动态路由

30 |
//html
 31 | <router-link :to="{
 32 |     name: 'article', params: { articleId: item._id }
 33 | }">
 34 | </router-link>
 35 | 
 36 | //js
 37 | this.$router.push({ path: ' ', params:' ' })
 38 | 

1.1,main.js里要用特殊的姿♂势导入路由才可执行:

39 |
const app = new Vue({
 40 |   el: 'app',
 41 |   router,
 42 |   store,
 43 |   render: h => h(App)
 44 | })
 45 | 
46 |

2,在vue组件里面只允许一个根节点,即:

47 |
<template>
 48 |   //要是>1的同级层就算再不乐意也得创建一个空div包裹所有层
 49 |   <div>
 50 |       .....
 51 |   </div>
 52 | </template>
 53 | 

3,使用formdata传输input上传图片

54 |
<form id="form" name="multiUploadForm" method="post" enctype="multipart/form-data">
 55 |    <input name="thisInput">
 56 | </from>
 57 | 
 58 | var data = new FormData(document.getElementById("form"));
 59 | //formdata也可以使用append方法添加数据
 60 | //data.append("other", value)
 61 | 

3.1,form的enctype选择“multipart/form-data”才可以上传多图
3.2,要想vue-resouce正确提交formdata数据就必须加“emulateJSON: true”参数

62 |
Vue.resource(API_ROOT + 'users{/id}', { emulateJSON: true });
 63 | 

3.4,每一条formdata数据必须带上name参数

64 |

4,marked+hightLight.js没什么好说,监听编辑框输入,预览框对应输出,对于图片处理我有自己的方法:将上传图片存入数组1,转为二进制保存在数组2,图片显示名字不显示地址(临时预览不上传服务器),预览端输出数组2显示图片,若用户更换图片顺序,索引图片名称对应位置重新排序数组2

65 |
//生成base64图片,临时预览,FileReader跟formdata一样为H5新增方法
 66 | var reader = new FileReader();
 67 | reader.readAsDataURL(thisFile);
 68 | //监听图片上传完毕,onload事件
 69 | reader.onload = function(e){
 70 |       self.insertPicAry.push( {
 71 |            result : this.result,
 72 |            name : thisFile.name,
 73 |            file : thisFile,
 74 |       );
 75 |       self.picAddress = thisFile.name;
 76 |  }
 77 | 

其他文本输出逻辑思路有:插入编辑功能的自动选中、区分、输出;用户手动选中区分、输出;二次进入编辑时赋值;各种状态判断等

78 |

5,token验证
涉及到用户修改数据都会加入token验证,还必须得是异步函数,验证完了才能执行下一步。vue-rouce提供了拦截器interceptos,在请求发出前获取用户登录后存储在sessionStorge的用户token,插入请求头,发出请求

79 |
//vue-resource拦截器
 80 | Vue.http.interceptors.push((request, next) => {
 81 |   Vue.http.headers.common['token'] = sessionStorage.token || '';
 82 |   next((response) => {
 83 |     //在响应之后传给then之前对response进行修改和逻辑判断
 84 |     if (response.status == 401) {
 85 |       store.state.loginJudge = false;
 86 |       sessionStorage.removeItem('token');
 87 |     }
 88 |   })
 89 | })
 90 | 
 91 | 
 92 | //token验证使用promise对象
 93 |  getUserLogin ( {commit}, opts){
 94 |     opts = opts || "";
 95 |     return new Promise((resolve, reject) => {
 96 |       api.getUserLogin(opts).then(function (res) {
 97 |         if (res.body.state) {
 98 |           resolve();
 99 |         }else {
100 |           reject(res.body.msg)
101 |         }
102 |       }).catch(function(e){
103 |          reject(e.body.msg)
104 |       })
105 |     })
106 |   },
107 | 
108 | 109 | 110 | ## “闪现”制作流程之(三): 三话后台 111 |

使用koa搭建服务器,使用MongoDB存储数据

112 |

讲讲main.js里各类插件处理各类问题:

113 |

1,路由匹配:koa-router

114 |
let koa = require('koa-router')();
115 | 
116 | //注册路由,router.routes()返回处理匹配到了请求对应的路由的router中间件
117 | //router.use([path], middleware, [...])  Use given middleware(s) before route callback.
118 | //router.use(),匹配到设置的接口地址后,进入回调中间件,再中间件里设置路由路径匹配对应后的回调函数
119 | koa.use('/users', users.routes(), users.allowedMethods());
120 | 

2,前后端分别配置两个域名,当然地址是本机,只是端口号不一样,既然是跨域就必须解决接口跨域的问题:kcros
3,想要通过地址访问本地存储的图片就必须暴露此服务器地址(文件夹位置):koa-static

121 |
let cors = require('kcors'),
122 |      static = require('koa-static');
123 | 
124 | //cors解决跨域问题
125 | app.use(cors());
126 | //static配置静态文件,可直接访问该文件夹下的文件
127 | app.use(static(__dirname + '/public/'));
128 | 

4,token签发认证:koa-jwt

129 |
app.use(jwt({
130 |   secret: 'Blink_1.0',
131 |   passthrough: true
132 | }));
133 | 
134 | 
135 | app.use(function *(next){
136 |   var ctx = this,
137 |       thisUrl = ctx.request.url;
138 |   console.log(thisUrl)
139 |   // 如果不是create或者checkOut路径,直接跳过该中间件
140 |   if (thisUrl.indexOf('create') === -1 && thisUrl.indexOf('checkOut') == -1) {
141 |     return yield next;
142 |   }
143 |   var token = ctx.request.headers.token || '';
144 |   if (token) {
145 |     var profile = jwt.verify(token, 'Blink_1.0');
146 |     if (profile) {
147 |       // 设置过期时间为1天
148 |       if (Date.now() - profile.original_iat  < 12 * 60 * 60 * 1000) {
149 |         yield next;
150 |       } else {
151 |         ctx.status = 401;
152 |         ctx.body = {
153 |           state: false,
154 |           msg: 'token已过期'
155 |         };
156 |       }
157 |     } else {
158 |       ctx.status = 401;
159 |       ctx.body = {
160 |         state: false,
161 |         msg: 'token认证失败'
162 |       }
163 |     }
164 |   } else {
165 |     ctx.status = 401;
166 |     ctx.body = {
167 |       state: false,
168 |       msg: 'token认证失败'
169 |     }
170 |   }
171 | });
172 | 

5,想要全局使用mongodb,就得赋值一个全局承诺

173 |
const mongoose = require('mongoose')
174 | 
175 | const mongooseDB = function () {
176 |     //重点在这一句,赋值一个全局的承诺。
177 |     mongoose.Promise = global.Promise
178 |   mongoose.connect('mongodb://127.0.0.1:27017/my_blog')
179 |   return mongoose.connection
180 | }
181 | 

敲黑板,划重点:
流程(二)前端里说过,图片是通过formdata形式提交数据,如何将数据流转为绝对地址保存在本地服务器:co-busboy

182 |
//上传控件
183 | var parse = require('co-busboy');
184 | var fs = require('fs');
185 | var os = require('os');
186 | var path = require('path');
187 | 
188 | while (part = yield parts){
189 |     //一定要对流进行处理,否则while循环会被卡住
190 |     //input上传文件就是个对象,包含了filename键值
191 |     if (part.filename) {
192 |       var filename = part.filename;
193 |       fileNames.push(filename)
194 |       var homeDir = path.resolve(__dirname, '..')
195 |       var newpath = homeDir + '/public/images/article/'+ filename;
196 |       //生成存储路径,要注意这里的newpath必须是绝对路径,否则Stream报错
197 |       var stream = fs.createWriteStream(newpath);
198 |       //写入文件流
199 |       part.pipe(stream);
200 | 
201 |     //不是input上传文件则为数组['键','值']形式
202 |     }else if(part.length) {
203 |       if (part[0].indexOf("subTags") > -1) {
204 |         subTags.push(part[1]);
205 |         opts["subTags"] = subTags;
206 |       }else {
207 |         opts[part[0]] = part[1];
208 |       }
209 |     }
210 | 
211 | 212 | -------------------------------------------------------------------------------- /front/src/components/reusable/topBar.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 78 | -------------------------------------------------------------------------------- /front/src/components/reusable/login.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 134 | -------------------------------------------------------------------------------- /front/src/components/query.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 170 | -------------------------------------------------------------------------------- /front/src/components/personal.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 204 | -------------------------------------------------------------------------------- /front/src/components/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 119 | -------------------------------------------------------------------------------- /front/src/components/create.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 560 | --------------------------------------------------------------------------------