├── src ├── store │ ├── actions.js │ ├── getters.js │ ├── mutation-types.js │ ├── state.js │ ├── mutations.js │ └── index.js ├── assets │ ├── bg.jpg │ ├── Wechat.jpg │ ├── avatar.jpg │ ├── cl_1.png │ ├── logo.png │ └── selfInfo.jpg ├── components │ ├── front │ │ ├── TagCloud.vue │ │ ├── MaskScreen.vue │ │ ├── BottomFooter.vue │ │ ├── RightContent.vue │ │ ├── RightSearch.vue │ │ ├── BreadCrumb.vue │ │ ├── ArticleList.vue │ │ ├── TopHeader.vue │ │ └── LeftEntry.vue │ ├── Iconfont.vue │ └── admin │ │ ├── Aside.vue │ │ └── TopHeader.vue ├── views │ ├── admin │ │ ├── SelfInfo.vue │ │ ├── User.vue │ │ ├── Admin.vue │ │ ├── ArticleList.vue │ │ └── Markdown.vue │ └── front │ │ ├── Catalog.vue │ │ ├── Article.vue │ │ ├── Container.vue │ │ ├── Home.vue │ │ ├── About.vue │ │ └── Login.vue ├── common │ ├── css │ │ ├── variable.scss │ │ └── reset.scss │ └── js │ │ ├── tool.js │ │ └── mixin.js ├── App.vue ├── serviceAPI.config.js ├── main.js └── router.js ├── articles ├── 5bf38b09d2fad55e96a30fbb_666.md ├── 5d2c24b96345ec09e0d99d34_66666.md ├── 5bf38b09d2fad55e96a30fbb_666.html ├── 5d2c24b96345ec09e0d99d34_66666.html ├── vue+node+mysql搭建个人博客(二).md ├── vue+node+mysql搭建个人博客(一).md ├── 5d2c3ebf6345ec09e0d99d35_555.md └── 5d3aa128f6d6eb0c9404ac1d_aaaaaa.md ├── babel.config.js ├── public ├── favicon.ico ├── favicon-1.ico └── index.html ├── version └── version-1.zip ├── server ├── jsonData │ ├── user.js │ └── article.js ├── api │ ├── root.js │ ├── tag.js │ ├── tencentCloud.js │ ├── tool.js │ ├── email.js │ ├── user.js │ └── article.js ├── router.js ├── database │ ├── schema │ │ ├── tag.js │ │ ├── user.js │ │ └── article.js │ └── init.js └── index.js ├── .babelrc ├── .gitignore ├── README.md ├── vue.config.js └── package.json /src/store/actions.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /articles/5bf38b09d2fad55e96a30fbb_666.md: -------------------------------------------------------------------------------- 1 | 666 -------------------------------------------------------------------------------- /articles/5d2c24b96345ec09e0d99d34_66666.md: -------------------------------------------------------------------------------- 1 | 666666 -------------------------------------------------------------------------------- /articles/5bf38b09d2fad55e96a30fbb_666.html: -------------------------------------------------------------------------------- 1 |

666

2 | -------------------------------------------------------------------------------- /articles/5d2c24b96345ec09e0d99d34_66666.html: -------------------------------------------------------------------------------- 1 |

666666

2 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | export const mobileFlag = state => state.mobileFlag -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const SET_MOBILE_FLAG = 'SET_MOBILE_FLAG' -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | const state = { 2 | mobileFlag: false 3 | } 4 | 5 | export default state -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/src/assets/bg.jpg -------------------------------------------------------------------------------- /public/favicon-1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/public/favicon-1.ico -------------------------------------------------------------------------------- /src/assets/Wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/src/assets/Wechat.jpg -------------------------------------------------------------------------------- /src/assets/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/src/assets/avatar.jpg -------------------------------------------------------------------------------- /src/assets/cl_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/src/assets/cl_1.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /version/version-1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/version/version-1.zip -------------------------------------------------------------------------------- /src/assets/selfInfo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Moon-Future/vue-node-blog/HEAD/src/assets/selfInfo.jpg -------------------------------------------------------------------------------- /server/jsonData/user.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "", 4 | "email": "", 5 | "password": "", 6 | "avatar": "", 7 | "website": "" 8 | } 9 | ] -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "component", 5 | { 6 | "libraryName": "element-ui", 7 | "styleLibraryName": "theme-chalk" 8 | } 9 | ] 10 | ] 11 | } -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | 3 | const mutations = { 4 | [types.SET_MOBILE_FLAG](state, flag) { 5 | state.mobileFlag = flag 6 | } 7 | } 8 | 9 | export default mutations -------------------------------------------------------------------------------- /server/api/root.js: -------------------------------------------------------------------------------- 1 | const checkRoot = function(ctx) { 2 | if (!ctx.session || !ctx.session.userInfo) { 3 | return {code: 500, message: '请先登陆'} 4 | } else { 5 | return {code: 200} 6 | } 7 | } 8 | 9 | module.exports = checkRoot -------------------------------------------------------------------------------- /server/jsonData/article.js: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | title: 'vue+node+mysql搭建个人博客(一)', 4 | summary: '', 5 | content: '', 6 | comment: [], 7 | user: 8023, 8 | tag: [1, 2, 3], 9 | createdTime: new Date().getTime(), 10 | } 11 | ] -------------------------------------------------------------------------------- /src/components/front/TagCloud.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/admin/SelfInfo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | 23 | server/secret.js 24 | package-lock.json 25 | -------------------------------------------------------------------------------- /server/router.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | const article = require('./api/article') 3 | const tag = require('./api/tag') 4 | const user = require('./api/user') 5 | const router = new Router({ 6 | prefix: '/api' 7 | }) 8 | 9 | router.use('/article', article.routes()) 10 | router.use('/tag', tag.routes()) 11 | router.use('/user', user.routes()) 12 | 13 | module.exports = router -------------------------------------------------------------------------------- /src/common/css/variable.scss: -------------------------------------------------------------------------------- 1 | // 颜色定义规范 2 | $color-white: #fff; 3 | $color-black: #000; 4 | $color-shallowgray: #fafbfc; 5 | $color-gray: #999; 6 | $color-deepgray: #4d4545; 7 | $color-green: #00CC33; 8 | $color-blue: #409EFF; 9 | $color-deepblue: #0066FF; 10 | $color-red: #FF0033; 11 | $color-active: #f298ae; 12 | $color-origin: #F7BA2A; 13 | $color-shallowgreend: #CCFFCC; 14 | $color-gray-1: #f5f5f5; -------------------------------------------------------------------------------- /server/database/schema/tag.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const tagSchema = new Schema({ 5 | name: { type: String, default: '', unique: true }, 6 | createTime: { type: Number, default: null }, 7 | updateTime: { type: Number, default: null }, 8 | }, { 9 | collections: 'tag' 10 | }) 11 | 12 | module.exports = mongoose.model('Tag', tagSchema, 'tag') -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-node-blog 2 | 3 | 2018.10.22 ToDoList 4 | 1. 重新设计页面布局 5 | 2. vue-cli 2 升级为 @vue/cli 3 6 | 3. express 改为 Koa2 7 | 4. Mysql 改为 Mongodb 8 | 5. 之前项目版本备份在 version 文件夹下 9 | 10 | 标签的 Props 应该有统一的顺序,依次为指令、属性和事件 11 | ``` 12 | 22 | ``` 23 | -------------------------------------------------------------------------------- /server/api/tag.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | const router = new Router() 3 | const Tag = require('../database/schema/tag') 4 | 5 | router.post('/getTag', async (ctx) => { 6 | try { 7 | const result = await Tag.find({}) 8 | ctx.body = {code: 200, message: result} 9 | } catch(err) { 10 | throw new Error(err) 11 | } 12 | }) 13 | 14 | router.post('/getTagInfo', async (ctx) => { 15 | 16 | }) 17 | 18 | module.exports = router -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /src/components/front/MaskScreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 25 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | // import Vue from 'vue' 2 | // import Vuex from 'vuex' 3 | import * as actions from './actions' 4 | import * as getters from './getters' 5 | import state from './state' 6 | import mutations from './mutations' 7 | import createLogger from 'vuex/dist/logger' 8 | 9 | Vue.use(Vuex) 10 | 11 | const debug = process.env.NODE_ENV !== 'production' 12 | 13 | export default new Vuex.Store({ 14 | actions, 15 | getters, 16 | state, 17 | mutations, 18 | strict: debug, 19 | plugins: debug ? [createLogger()] : [] 20 | }) -------------------------------------------------------------------------------- /src/serviceAPI.config.js: -------------------------------------------------------------------------------- 1 | export const apiUrl = { 2 | register: '/api/user/register', 3 | login: '/api/user/login', 4 | logout: '/api/user/logout', 5 | getSession: '/api/user/getSession', 6 | upload: '/api/user/upload', 7 | 8 | insertArticle: '/api/article/insertArticle', 9 | deleteArticle: '/api/article/deleteArticle', 10 | getArticle: '/api/article/getArticle', 11 | getArticleInfo: '/api/article/getArticleInfo', 12 | getCatalog: '/api/article/getCatalog', 13 | getHotArticle: '/api/article/getArticle', 14 | 15 | getTag: '/api/tag/getTag' 16 | } -------------------------------------------------------------------------------- /src/views/admin/User.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /server/database/schema/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const userSchema = new Schema({ 5 | name: { type: String, default: '' }, 6 | email: { type: String, default: '' }, 7 | password: { type: String, default: '' }, 8 | avatar: { type: String, default: '' }, 9 | website: { type: String, default: '' }, 10 | remind: { type: Number, default: 0 }, // 邮件提醒 11 | root: { type: Number, default: 0 }, 12 | createTime: { type: Number, default: null }, 13 | updateTime: { type: Number, default: null } 14 | }, { 15 | collections: 'user' 16 | }) 17 | 18 | module.exports = mongoose.model('User', userSchema, 'user') -------------------------------------------------------------------------------- /server/api/tencentCloud.js: -------------------------------------------------------------------------------- 1 | // 引入模块 2 | const COS = require('cos-nodejs-sdk-v5') 3 | const { tencentCloud } = require('../secret.js') 4 | 5 | // 创建实例 6 | const cos = new COS({ 7 | SecretId: tencentCloud.SecretId, 8 | SecretKey: tencentCloud.SecretKey 9 | }); 10 | 11 | 12 | const cosUpload = function(fileName, filePath) { 13 | // 分片上传 14 | return new Promise((resolve, reject) => { 15 | cos.sliceUploadFile({ 16 | Bucket: tencentCloud.Bucket, 17 | Region: 'ap-guangzhou', 18 | Key: fileName, 19 | FilePath: filePath 20 | }, function (err, data) { 21 | resolve(data); 22 | }); 23 | }) 24 | } 25 | 26 | module.exports = cosUpload 27 | -------------------------------------------------------------------------------- /src/common/js/tool.js: -------------------------------------------------------------------------------- 1 | export function dateFormat(date, format) { 2 | date = typeof date === 'number' ? new Date(date) : date 3 | let o = { 4 | 'M+': date.getMonth() + 1, 5 | 'd+': date.getDate(), 6 | 'h+': date.getHours(), 7 | 'm+': date.getMinutes(), 8 | 's+': date.getSeconds() 9 | } 10 | if(/(y+)/i.test(format)){ 11 | format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 12 | } 13 | for(let k in o){ 14 | if(new RegExp('(' + k + ')').test(format)){ 15 | format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) 16 | } 17 | } 18 | return format 19 | } -------------------------------------------------------------------------------- /src/common/js/mixin.js: -------------------------------------------------------------------------------- 1 | export const computeStyleMixin = { 2 | mounted() { 3 | this.routeWacth() 4 | }, 5 | methods: { 6 | routeWacth() { 7 | this.$nexttick(() => { 8 | if (this.$route.path === '/') { 9 | this.$refs.bgVideo.style.position = 'absolute' 10 | this.$refs.bgVideo.style.top = this.height + 'px' 11 | this.homeFlag = true 12 | } else { 13 | this.$refs.bgVideo.style.position = 'fixed' 14 | this.$refs.bgVideo.style.top = '50px' 15 | this.homeFlag = false 16 | } 17 | }) 18 | } 19 | }, 20 | watch: { 21 | $route() { 22 | this.routeWacth() 23 | } 24 | }, 25 | } -------------------------------------------------------------------------------- /server/database/init.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const db = 'mongodb://localhost:27017/myBlog' 3 | const maxConnetTimes = 3 4 | 5 | const connect = () => { 6 | return new Promise((resolve, reject) => { 7 | //连接数据库 8 | mongoose.connect(db) 9 | 10 | //增加数据库连接的事件监听 11 | mongoose.connection.on('disconnected',()=>{ 12 | //进行重连 13 | mongoose.connect(db) 14 | }) 15 | 16 | //数据库出现错误的时候 17 | mongoose.connection.on('error',err=>{ 18 | console.log(err) 19 | mongoose.connect(db) 20 | }) 21 | 22 | //链接打开的时候 23 | mongoose.connection.once('open',()=>{ 24 | console.log('MongoDB Connected successfully!') 25 | resolve() 26 | }) 27 | }) 28 | } 29 | 30 | module.exports = connect -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const CompressionPlugin = require('compression-webpack-plugin') 2 | 3 | module.exports = { 4 | devServer: { 5 | proxy: { 6 | '/api': { 7 | target: 'http://localhost:3002/api', 8 | changeOrigin: true, 9 | pathRewrite: { 10 | '^/api': '' 11 | } 12 | } 13 | } 14 | }, 15 | productionSourceMap: false, 16 | configureWebpack: { 17 | externals: { 18 | 'vue': 'Vue', 19 | 'vue-router': 'VueRouter', 20 | 'axios': 'axios', 21 | 'vuex': 'Vuex', 22 | 'element-ui': 'ELEMENT', 23 | }, 24 | }, 25 | configureWebpack: config => { 26 | if (process.env.NODE_ENV === 'production') { 27 | return { 28 | plugins: [new CompressionPlugin({ 29 | test: /\.js$|\.html&|\.css/, 30 | threshold: 10204, 31 | deleteOriginalAssets: false 32 | })] 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/components/front/BottomFooter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /server/database/schema/article.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const articleSchema = new Schema({ 5 | title: { type: String, default: '' }, 6 | summary: { type: String, default: '' }, 7 | content: { type: String, default: '' }, 8 | html: { type: String, default: '' }, 9 | comment: { type: Array, default: [] }, 10 | user: {type: Schema.Types.ObjectId, ref: 'User'}, 11 | tag: [{type: Schema.Types.ObjectId, ref: 'Tag'}], 12 | createTime: { type: Number, default: null }, 13 | updateTime: { type: Number, default: null }, 14 | view: { type: Number, default: 0 }, 15 | like: { type: Number, default: 0 }, 16 | reprint: { type: String, default: '' }, // 类型 {'': 原创, 'url': 转载链接} 17 | state: { type: Number, default: 1 }, // 状态 {0: 删除, 1: 显示-发布, 2: 不显示-存稿} 18 | }, { 19 | collections: 'article' 20 | }) 21 | 22 | module.exports = mongoose.model('Article', articleSchema, 'article') -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const app = new Koa() 3 | const session = require('koa-session') 4 | const connect = require('./database/init') 5 | const bodyParser = require('koa-bodyparser') 6 | const cors = require('koa2-cors') 7 | const static = require('koa-static') 8 | const path = require('path') 9 | const router = require('./router') 10 | 11 | ;(async () => { 12 | await connect() 13 | })() 14 | 15 | const CONFIG = { 16 | key: 'koa:sess', 17 | maxAge: 86400000, 18 | autoCommit: true, 19 | overwrite: true, 20 | httpOnly: true, 21 | signed: true, 22 | rolling: false, 23 | renew: false 24 | } 25 | app.keys = ['login secret'] 26 | app.use(session(CONFIG, app)); 27 | 28 | app.use(static(path.join(__dirname, '../dist'))); 29 | app.use(bodyParser()) 30 | app.use(cors()) 31 | app.use(router.routes()).use(router.allowedMethods()) 32 | app.listen(3002, () => { 33 | console.log('listen at port 3002...') 34 | }) -------------------------------------------------------------------------------- /src/components/Iconfont.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | 32 | 45 | 46 | -------------------------------------------------------------------------------- /server/api/tool.js: -------------------------------------------------------------------------------- 1 | const request = require('request') 2 | 3 | function getRandom(start, end, size) { 4 | let allRandms = [] 5 | size = size ? (size > end - start ? end - start : size) : 1 6 | for (let i = start; i <= end; i++) { 7 | allRandms.push(i) 8 | } 9 | allRandms.sort(() => { 10 | return 0.5 - Math.random() 11 | }) 12 | return size == 1 ? allRandms[0] : allRandms.slice(0, size) 13 | } 14 | 15 | function dateFormat(date, format) { 16 | date = typeof date === 'number' ? new Date(date) : date 17 | let o = { 18 | 'M+': date.getMonth() + 1, 19 | 'd+': date.getDate(), 20 | 'h+': date.getHours(), 21 | 'm+': date.getMinutes(), 22 | 's+': date.getSeconds() 23 | } 24 | if(/(y+)/i.test(format)){ 25 | format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) 26 | } 27 | for(let k in o){ 28 | if(new RegExp('(' + k + ')').test(format)){ 29 | format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)) 30 | } 31 | } 32 | return format 33 | } 34 | 35 | module.exports = { 36 | getRandom, 37 | dateFormat 38 | } -------------------------------------------------------------------------------- /server/api/email.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); 2 | const { emailInfo } = require('../secret.js') 3 | /* 4 | emailInfo: { 5 | account = { 6 | user: 'xxxxxx@qq.com', 7 | // 这里密码不是qq密码,是你设置的smtp授权码 8 | pass: 'xxxxxx', 9 | } 10 | } 11 | */ 12 | const account = emailInfo.account 13 | 14 | const transporter = nodemailer.createTransport({ 15 | // host: 'smtp.qq.email', 16 | service: 'qq', 17 | port: 465, 18 | secure: true, // true for 465, false for other ports 587 19 | auth: { 20 | user: account.user, // generated ethereal user 21 | pass: account.pass // generated ethereal password 22 | } 23 | }); 24 | 25 | var mailOptions = { 26 | from: 'LeoChan <236338364@qq.com>', // sender address 27 | to: '236338364@qq.com', // list of receivers 28 | subject: 'vue-myBlog', // Subject line 29 | // text: '有新注册用户', // plain text body 30 | // html: '你好哟' // html body 31 | }; 32 | 33 | const sendMsg = { 34 | newUser: '有新的注册用户', 35 | email: 'Email', 36 | name: 'Name', 37 | website: 'Website', 38 | time: '时间', 39 | newArticle: '有新的文章', 40 | title: 'Title', 41 | content: 'Content' 42 | } 43 | 44 | module.exports = {transporter, mailOptions, sendMsg}; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-node-blog", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.18.0", 11 | "cos-nodejs-sdk-v5": "^2.4.14", 12 | "element-ui": "^2.10.1", 13 | "formidable": "^1.2.1", 14 | "github-markdown-css": "^2.10.0", 15 | "highlight.js": "^9.13.1", 16 | "jinrishici": "^1.0.6", 17 | "koa": "^2.6.1", 18 | "koa-bodyparser": "^4.2.1", 19 | "koa-router": "^7.4.0", 20 | "koa-session": "^5.10.0", 21 | "koa-static": "^5.0.0", 22 | "koa2-cors": "^2.0.6", 23 | "marked": "^0.5.1", 24 | "mongoose": "^5.3.5", 25 | "nodemailer": "^4.7.0", 26 | "vue": "^2.5.17", 27 | "vue-image-crop-upload": "^2.5.0", 28 | "vue-lazyload": "^1.2.6", 29 | "vue-router": "^3.0.1", 30 | "vue-simplemde": "^0.4.9", 31 | "vuex": "^3.0.1" 32 | }, 33 | "devDependencies": { 34 | "@vue/cli-plugin-babel": "^3.0.5", 35 | "@vue/cli-service": "^3.0.5", 36 | "babel-plugin-component": "^1.1.1", 37 | "compression-webpack-plugin": "^3.0.0", 38 | "node-sass": "^4.9.4", 39 | "sass-loader": "^7.1.0", 40 | "vue-template-compiler": "^2.5.17" 41 | }, 42 | "postcss": { 43 | "plugins": { 44 | "autoprefixer": {} 45 | } 46 | }, 47 | "browserslist": [ 48 | "> 1%", 49 | "last 2 versions", 50 | "not ie <= 8" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /src/common/css/reset.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 3 | * http://cssreset.com 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video, input { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | font-size: 100%; 22 | font-weight: normal; 23 | vertical-align: baseline; 24 | } 25 | 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, menu, nav, section { 29 | display: block; 30 | } 31 | 32 | body { 33 | line-height: 1; 34 | } 35 | 36 | blockquote, q { 37 | quotes: none; 38 | } 39 | 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: none; 43 | } 44 | 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | 50 | /* custom */ 51 | 52 | a { 53 | //color: #7e8c8d; 54 | -webkit-backface-visibility: hidden; 55 | text-decoration: none; 56 | } 57 | 58 | li { 59 | list-style: none; 60 | } 61 | 62 | html, body { 63 | -webkit-text-size-adjust: none; 64 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 65 | height: 100%; 66 | } 67 | 68 | .markdown-body { 69 | text-align: left; 70 | } 71 | 72 | .iconfont { 73 | width: 1em; 74 | height: 1em; 75 | vertical-align: -0.15em; 76 | fill: currentColor; 77 | overflow: hidden; 78 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 沉酿 13 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/front/RightContent.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 38 | 39 | 72 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store/index' 5 | // import axios from 'axios' 6 | // import 'element-ui/lib/theme-chalk/index.css' 7 | // import 'github-markdown-css/github-markdown.css' 8 | import 'highlight.js/styles/vs.css' 9 | import VueLazyload from 'vue-lazyload' 10 | // import { 11 | // Dropdown, 12 | // DropdownMenu, 13 | // DropdownItem, 14 | // Menu, 15 | // Submenu, 16 | // MenuItem, 17 | // MenuItemGroup, 18 | // Input, 19 | // Button, 20 | // Table, 21 | // TableColumn, 22 | // Popover, 23 | // Breadcrumb, 24 | // BreadcrumbItem, 25 | // Form, 26 | // FormItem, 27 | // Tabs, 28 | // TabPane, 29 | // Row, 30 | // Col, 31 | // Badge, 32 | // Card, 33 | // Timeline, 34 | // TimelineItem, 35 | // Loading, 36 | // MessageBox, 37 | // Message 38 | // } from 'element-ui'; 39 | 40 | Vue.config.productionTip = false 41 | Vue.prototype.$http = axios 42 | Vue.use(VueLazyload) 43 | // Vue.use(Dropdown); 44 | // Vue.use(DropdownMenu); 45 | // Vue.use(DropdownItem); 46 | // Vue.use(Menu); 47 | // Vue.use(Submenu); 48 | // Vue.use(MenuItem); 49 | // Vue.use(MenuItemGroup); 50 | // Vue.use(Input); 51 | // Vue.use(Button); 52 | // Vue.use(Table); 53 | // Vue.use(TableColumn); 54 | // Vue.use(Popover); 55 | // Vue.use(Breadcrumb); 56 | // Vue.use(BreadcrumbItem); 57 | // Vue.use(Form); 58 | // Vue.use(FormItem); 59 | // Vue.use(Tabs); 60 | // Vue.use(TabPane); 61 | // Vue.use(Row); 62 | // Vue.use(Col); 63 | // Vue.use(Badge); 64 | // Vue.use(Card); 65 | // Vue.use(Timeline); 66 | // Vue.use(TimelineItem); 67 | 68 | // Vue.use(Loading.directive); 69 | 70 | // Vue.prototype.$loading = Loading.service; 71 | // Vue.prototype.$msgbox = MessageBox; 72 | // // Vue.prototype.$alert = MessageBox.alert; 73 | // Vue.prototype.$confirm = MessageBox.confirm; 74 | // // Vue.prototype.$prompt = MessageBox.prompt; 75 | // // Vue.prototype.$notify = Notification; 76 | // Vue.prototype.$message = Message; 77 | 78 | new Vue({ 79 | router, 80 | store, 81 | render: h => h(App) 82 | }).$mount('#app') 83 | -------------------------------------------------------------------------------- /src/views/admin/Admin.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 87 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | // import Vue from 'vue' 2 | // import Router from 'vue-router' 3 | import Home from './views/front/Home.vue' 4 | import Admin from './views/admin/Admin.vue' 5 | import { apiUrl } from '@/serviceAPI.config.js' 6 | 7 | Vue.use(VueRouter) 8 | 9 | const router = new VueRouter({ 10 | // mode: 'history', 11 | base: process.env.BASE_URL, 12 | routes: [ 13 | { 14 | path: '/', 15 | name: 'Home', 16 | component: Home, 17 | meta: { 18 | keepAlive: false 19 | } 20 | }, 21 | { 22 | path: '/article/:id', 23 | name: 'Article', 24 | component: () => import('@/views/front/Article.vue'), 25 | meta: { 26 | keepAlive: false 27 | } 28 | }, 29 | { 30 | path: '/catalog', 31 | name: 'Catalog', 32 | component: () => import('@/views/front/Catalog.vue'), 33 | meta: { 34 | keepAlive: false 35 | } 36 | }, 37 | { 38 | path: '/about', 39 | name: 'About', 40 | component: () => import(/* webpackChunkName: "about" */ './views/front/About.vue') 41 | }, 42 | { 43 | path: '/login', 44 | name: 'Login', 45 | component: () => import('@/views/front/Login.vue') 46 | }, 47 | { 48 | path: '/admin', 49 | name: 'Admin', 50 | component: Admin, 51 | meta: { 52 | requireAuth: true 53 | }, 54 | children: [ 55 | { 56 | path: 'selfinfo', 57 | name: 'SelfInfo', 58 | meta: { 59 | requireAuth: true 60 | }, 61 | component: () => import('@/views/admin/SelfInfo.vue') 62 | }, 63 | { 64 | path: 'articles', 65 | name: 'Articles', 66 | meta: { 67 | requireAuth: true 68 | }, 69 | component: () => import('@/views/admin/ArticleList.vue') 70 | }, 71 | { 72 | path: 'markdown', 73 | name: 'Markdown', 74 | meta: { 75 | requireAuth: true 76 | }, 77 | component: () => import('@/views/admin/Markdown.vue') 78 | } 79 | ] 80 | }, 81 | ] 82 | }) 83 | 84 | router.beforeEach((to, from, next) => { 85 | if (to.meta.requireAuth) { 86 | axios.post(apiUrl.getSession).then(res => { 87 | if (res.data.code === 200) { 88 | next() 89 | } else { 90 | next({path: '/'}) 91 | } 92 | }) 93 | } else if (to.name === 'Login') { 94 | axios.post(apiUrl.getSession).then(res => { 95 | if (res.data.code === 200) { 96 | next({path: '/admin/selfinfo'}) 97 | } else { 98 | next() 99 | } 100 | }) 101 | } else { 102 | next() 103 | } 104 | }) 105 | 106 | export default router 107 | -------------------------------------------------------------------------------- /src/components/front/RightSearch.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 69 | 70 | 111 | -------------------------------------------------------------------------------- /src/views/front/Catalog.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 71 | 72 | 100 | -------------------------------------------------------------------------------- /src/components/admin/Aside.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 97 | 98 | 123 | -------------------------------------------------------------------------------- /src/components/front/BreadCrumb.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 94 | 95 | 142 | -------------------------------------------------------------------------------- /server/api/user.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | const fs = require('fs') 3 | const path = require('path') 4 | const formidable = require('formidable'); 5 | const router = new Router() 6 | const User = require('../database/schema/user') 7 | const cosUpload = require('./tencentCloud.js') 8 | const avatarDafault = 'cl8023-1255423800.cos.ap-guangzhou.myqcloud.com/avatar/default.jpg' 9 | const {transporter, mailOptions, sendMsg} = require('./email') 10 | const {dateFormat} = require('./tool') 11 | 12 | router.post('/register', async (ctx) => { 13 | try { 14 | const data = ctx.request.body.data 15 | const user = new User({ 16 | name: data.name, 17 | email: data.email, 18 | password: data.password, 19 | avatar: avatarDafault, 20 | website: data.website, 21 | createTime: new Date().getTime() 22 | }) 23 | user.save() 24 | ctx.body = {code: 200, message: '注册成功'} 25 | mailOptions.html = '

' + sendMsg.newUser + '

' 26 | + '

' + sendMsg.email + ': ' + data.email + '

' 27 | + '

' + sendMsg.name + ': ' + data.name + '

' 28 | + '

' + sendMsg.website + ': ' + data.website + '

' 29 | + '

' + sendMsg.time + ': ' + dateFormat(new Date, 'yyyy-MM-dd hh:mm:ss') + '

'; 30 | transporter.sendMail(mailOptions, (error, info) => { 31 | if (error) { 32 | return console.log(error); 33 | } 34 | }); 35 | } catch(err) { 36 | throw new Error(err) 37 | } 38 | }) 39 | 40 | router.get('/registerByJson', async (ctx) => { 41 | try { 42 | const fileContent = fs.readFileSync(__dirname, '../jsonData/user.js', 'utf-8') 43 | for (let i = 0, len = fileContent.length; i < len; i++) { 44 | const data = fileContent[i] 45 | const user = new User({ 46 | name: data.name, 47 | email: data.email, 48 | password: data.password, 49 | avatar: avatarDafault, 50 | website: data.website, 51 | createTime: new Date().getTime() 52 | }) 53 | user.save() 54 | } 55 | ctx.body = {code: 200, message: '注册成功'} 56 | } catch(err) { 57 | throw new Error(err) 58 | } 59 | }) 60 | 61 | router.post('/login', async (ctx) => { 62 | try { 63 | const data = ctx.request.body.data 64 | const email = data.email 65 | const password = data.password 66 | const result = await User.find({email: email}) 67 | if (result.length === 0) { 68 | ctx.body = {code: 500, message: '用户不存在'} 69 | } else if(result[0].password !== password) { 70 | ctx.body = {code: 500, message: '密码错误'} 71 | } else { 72 | ctx.session.userInfo = {id: result[0]._id, name: result[0].name, avatar: result[0].avatar, root: result[0].root} 73 | ctx.body = {code: 200, message: '登陆成功'} 74 | } 75 | } catch(err) { 76 | throw new Error(err) 77 | } 78 | }) 79 | 80 | router.post('/logout', async (ctx) => { 81 | try { 82 | ctx.session = null 83 | ctx.body = {code: 200, message: '已退出'} 84 | } catch(err) { 85 | throw new Error(err) 86 | } 87 | }) 88 | 89 | router.post('/getSession', async (ctx) => { 90 | try { 91 | const userInfo = ctx.session.userInfo 92 | if (userInfo) { 93 | ctx.body = {code: 200, message: userInfo} 94 | } else { 95 | ctx.body = {code: 500, message: '请先登陆'} 96 | } 97 | } catch(err) { 98 | throw new Error(err) 99 | } 100 | }) 101 | 102 | router.post('/upload', async (ctx) => { 103 | try { 104 | const userInfo = ctx.session.userInfo 105 | const {fields, files} = await formParse(ctx.req) 106 | const avatarData = files.avatar 107 | const filePath = avatarData.path 108 | const fileName = '/avatar/' + userInfo.id + '.jpg' 109 | const result = await cosUpload(fileName, filePath) 110 | ctx.session.userInfo.avatar = result.Location 111 | await User.update({_id: userInfo.id}, {avatar: result.Location}) 112 | ctx.body = {code: 200, message: {avatar: result.Location}} 113 | } catch(err) { 114 | throw new Error(err) 115 | } 116 | }) 117 | 118 | function formParse(req) { 119 | let form = new formidable.IncomingForm() 120 | return new Promise((resolve, reject) => { 121 | form.parse(req, (error, fields, files) => { 122 | if (error) { 123 | reject(error) 124 | } else { 125 | resolve({fields, files}) 126 | } 127 | }) 128 | }) 129 | } 130 | 131 | module.exports = router -------------------------------------------------------------------------------- /src/components/front/ArticleList.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 78 | 79 | 158 | -------------------------------------------------------------------------------- /src/views/admin/ArticleList.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 131 | 132 | 137 | -------------------------------------------------------------------------------- /src/components/front/TopHeader.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 98 | 99 | -------------------------------------------------------------------------------- /src/components/admin/TopHeader.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 106 | 186 | -------------------------------------------------------------------------------- /src/views/front/Article.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 136 | 137 | 187 | -------------------------------------------------------------------------------- /src/views/front/Container.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 151 | 152 | 202 | 203 | -------------------------------------------------------------------------------- /server/api/article.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const Router = require('koa-router') 4 | const marked = require('marked') 5 | const router = new Router() 6 | const Article = require('../database/schema/article') 7 | const Tag = require('../database/schema/tag') 8 | const ObjectId = require('mongoose').Types.ObjectId 9 | const checkRoot = require('./root') 10 | const filePath = path.join(__dirname, '../../articles') 11 | 12 | marked.setOptions({ 13 | highlight: function (code) { 14 | return require('highlight.js').highlightAuto(code).value; 15 | } 16 | }) 17 | 18 | router.post('/insertArticle', async (ctx) => { 19 | try { 20 | const checkResult = checkRoot(ctx) 21 | if (checkResult.code === 500) { 22 | ctx.body = checkResult 23 | return 24 | } 25 | const root = ctx.session.userInfo.root 26 | const data = ctx.request.body.data 27 | const currentTime = new Date().getTime() 28 | const tags = data.tags || [] 29 | const html = marked(data.content) 30 | let tagsId = [], result = [], fileName = '', message = '' 31 | if (tags.length !== 0) { 32 | let insertTags = [], tagResult = [] 33 | tags.forEach(ele => { 34 | if (ele.id.indexOf('newTag') !== -1) { 35 | insertTags.push({name: ele.name, createTime: currentTime}) 36 | } else { 37 | tagsId.push(ObjectId(ele.id)) 38 | } 39 | }) 40 | tagResult = await Tag.insertMany(insertTags) 41 | tagResult.forEach(ele => { 42 | tagsId.push(ObjectId(ele._id)) 43 | }) 44 | } 45 | let htmlTags = html.match(/.*|<(.|\n)*?<\/.*>.*/g) 46 | let summary = data.summary 47 | // let count = 0 48 | // let limit = 0 49 | // for(let i = 0, len = htmlTags.length; i < len; i++) { 50 | // if (htmlTags[i] === '') { 51 | // summary += '\n' 52 | // } 53 | // if (htmlTags[i].match(/^<(.)/)) { 54 | // let tag = htmlTags[i].match(/^<(.)/)[1] 55 | // if (tag === 'h') { 56 | // count += 1 57 | // } 58 | // if (count > limit && tag === 'h') { 59 | // break 60 | // } 61 | // } 62 | // summary += htmlTags[i] 63 | // } 64 | // summary = summary.replace(/\'/g, '"') 65 | if (data.id === '-1') { 66 | // 新增 67 | const article = new Article({ 68 | title: data.title, 69 | summary, 70 | content: '', 71 | html: '', 72 | user: ObjectId(data.user), 73 | tag: tagsId, 74 | createTime: currentTime, 75 | state: root === 0 ? 2 : data.state 76 | }) 77 | result = await article.save() 78 | fileName = result._id + '_' + data.title 79 | message = '发布成功' 80 | } else { 81 | // 更新 82 | await Article.update({_id: data.id}, { 83 | title: data.title, 84 | summary, 85 | tag: tagsId, 86 | updateTime: currentTime, 87 | state: root === 0 ? 2 : data.state 88 | }) 89 | fileName = data.id + '_' + data.title 90 | message = '更新成功' 91 | if (data.title !== data.originTitle) { 92 | fs.renameSync(path.join(filePath, data.id + '_' + data.originTitle + '.md'), path.join(filePath, fileName + '.md')); 93 | fs.renameSync(path.join(filePath, data.id + '_' + data.originTitle + '.html'), path.join(filePath, fileName + '.html')); 94 | } 95 | } 96 | fs.writeFileSync(path.join(filePath, fileName + '.md'), data.content, 'utf-8') 97 | fs.writeFileSync(path.join(filePath, fileName + '.html'), html, 'utf-8') 98 | ctx.body = {code: 200, message: message} 99 | } catch(err) { 100 | throw new Error(err) 101 | } 102 | }) 103 | 104 | router.post('/getArticle', async (ctx) => { 105 | try { 106 | const checkResult = checkRoot(ctx) 107 | const data = ctx.request.body.data 108 | let result 109 | if (data.admin) { 110 | // 后台管理列表 111 | if (checkResult.code === 500) { 112 | ctx.body = checkResult 113 | return 114 | } 115 | result = await Article.find({}, {summary: 0, content: 0, html: 0, comment: 0}).populate('tag') 116 | } else if (data.summary) { 117 | // home首页列表 118 | result = await Article.find({state: 1}, {content: 0, html: 0, comment: 0}).populate('tag') 119 | } else if (data.hot) { 120 | // 热门列表 121 | result = await Article.find({state: 1}, {content: 0, html: 0, comment: 0}).populate('tag') 122 | } else if (data.catalog) { 123 | // 归档目录 124 | result = await Article.find({state: 1}, {summary: 0, content: 0, html: 0, comment: 0}).sort({createTime:-1}).populate('tag') 125 | } else if (data.markdown) { 126 | // markdown 修改内容 127 | if (checkResult.code === 500) { 128 | ctx.body = checkResult 129 | return 130 | } 131 | result = await Article.find({_id: data.id}, {comment: 0}).populate('tag') 132 | if (result.length !== 0) { 133 | const title = result[0].title 134 | const content = fs.readFileSync(path.join(filePath, `${data.id}_${title}.md`), 'utf-8') 135 | result[0].content = content 136 | } 137 | } else { 138 | // 文章内容 139 | result = await Article.find({_id: data.id}, {content: 0, summary: 0, comment: 0}).populate('tag') 140 | if (result.length !== 0 && data.chapter === undefined) { 141 | await Article.update({_id: data.id}, {view: result[0].view + 1}) 142 | } 143 | if (result.length !== 0) { 144 | const title = result[0].title 145 | const html = fs.readFileSync(path.join(filePath, `${data.id}_${title}.html`), 'utf-8') 146 | result[0].html = html 147 | } 148 | } 149 | ctx.body = {code: 200, message: result} 150 | } catch(err) { 151 | throw new Error(err) 152 | } 153 | }) 154 | 155 | router.post('/deleteArticle', async (ctx) => { 156 | try { 157 | const checkResult = checkRoot(ctx) 158 | if (checkResult.code === 500) { 159 | ctx.body = checkResult 160 | return 161 | } 162 | const data = ctx.request.body.data 163 | const userInfo = ctx.session.userInfo 164 | const id = data.id, title = data.title 165 | const article = await Article.find({_id: id}, {summary: 0, content: 0, html: 0, comment: 0}) 166 | if (article[0].user != userInfo.id && userInfo.root !== 1) { 167 | ctx.body = {code: 200, message: '没有权限'} 168 | return 169 | } 170 | await Article.remove({_id: data.id}) 171 | fs.unlinkSync(path.join(filePath, `${id}_${title}.md`)) 172 | fs.unlinkSync(path.join(filePath, `${id}_${title}.html`)) 173 | ctx.body = {code: 200, message: '删除成功'} 174 | } catch(err) { 175 | throw new Error(err) 176 | } 177 | }) 178 | 179 | module.exports = router -------------------------------------------------------------------------------- /src/components/front/LeftEntry.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 115 | 116 | 201 | -------------------------------------------------------------------------------- /src/views/front/Home.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 131 | 132 | 235 | 236 | -------------------------------------------------------------------------------- /src/views/front/About.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 119 | 120 | 230 | 231 | -------------------------------------------------------------------------------- /src/views/admin/Markdown.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 176 | 177 | 229 | -------------------------------------------------------------------------------- /src/views/front/Login.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 211 | 212 | 273 | -------------------------------------------------------------------------------- /articles/vue+node+mysql搭建个人博客(二).md: -------------------------------------------------------------------------------- 1 | # 组件 2 | 3 | ### 父组件向子组件通信 4 | 父组件通过 props 向子组件通信,在父组件内可通过 this.$children 来读取子组件中的值。props 是单向绑定,不可在子组件中赋值。 5 | #### 创建单文件组件 6 | 在 src/components 目录下创建文件夹 common 用于存放公共组件,并在 common 下创建单文件组件 LeftNavItem.vue(子组件) 7 | ```js 8 | 13 | 14 | 20 | 21 | 24 | ``` 25 | 26 | #### 引入组件 27 | 在 src/components/page/Blog.vue(父组件) 里引入组件 LeftNavItem.vue 28 | ```js 29 | 37 | 38 | 57 | 58 | 68 | ``` 69 | 70 | 这里父组件是 Blog.vue,子组件是 LeftNavItem.vue,父组件中调用子组件 71 | ```js 72 | 73 | ``` 74 | 其中 :childMes 中的 childMes 是要传递到子组件中的变量,即对应 LeftNavItem.vue 中 props 属性中的值,可以传递多个变量 75 | ```js 76 | props: ['childMes', 'childMes2', 'childMes3'], 77 | ``` 78 | - props 中的变量和 data 的变量一样,直接使用 this.childMes 获取值, 79 | - :childMes="message" 中的 "message" 即是父组件自身 data 中的变量, 80 | 这样在父组件中更改 message 的值,子组件便会得到相应的更新。 81 | - 方法 getChild 中的 this.$children[0].childMes 可以的到子组件中 childMes的值,但不可给其赋值,否值的话会报错,只能通过父组件中的 message 来更改值。 82 | 83 | ### 子组件向父组件通信 84 | 父组件向子组件传递事件方法,子组件通过 $emit 触发事件,回调给父组件。使用$parent可以访问父组件的数据 85 | 在 LeftNavItem.vue 中增加代码 86 | ```js 87 | 93 | 94 | 106 | 107 | 110 | ``` 111 | Blog.vue 增加代码 112 | ```js 113 | 124 | 125 | 149 | 150 | 160 | ``` 161 | - 父组件 Blog.vue 中通过 162 | 163 | ```js 164 | 165 | ``` 166 | 将事件方法 mesFunc 传递到子组件 167 | - 子组件 LeftNavItem.vue 中通过 $emit 可以触发事件,并传递数据 168 | 169 | ```js 170 | this.$emit('mesFunc', 'from children') 171 | // 第一个参数:父组件传递过来的事件 172 | // 第二个参数:要传递到父组件的数据 173 | ``` 174 | 175 | 176 | # 状态管理(Vuex) 177 | [Vuex 是什么](https://vuex.vuejs.org/zh/) 178 | 179 | Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 180 | 以上是官方介绍,通俗点将有点类似全局变量,来管理各种状态。 181 | Vuex 中 Store 的模版化定义如下: 182 | ```js 183 | import Vue from 'vue' 184 | import Vuex from 'vuex' 185 | Vue.use(Vuex) 186 | const store = new Vuex.Store({ 187 | state: { 188 | }, 189 | actions: { 190 | }, 191 | mutations: { 192 | }, 193 | getters: { 194 | }, 195 | modules: { 196 | } 197 | }) 198 | export default store 199 | ``` 200 | - state:定义了应用状态的数据结构,"状态值"的存放处 201 | - action:定义提交触发更改信息的描述,常见的例子有从服务端获取数据,在数据获取完成后会调用 store.commit() 来调用更改 Store 中的状态。可以在组件中使用 dispatch 来发出 actions 202 | - mutations:唯一允许更新应用状态的地方 203 | - getters:Getters 允许组件从 Store 中获取数据 204 | - modules:modules 对象允许将单一的 Store 拆分为多个 Store 的同时保存在单一的状态树中 205 | 206 | ## 初始化 state 207 | 在 scr 下新建文件夹 store,并在 store 里新建文件 index.js 208 | ```js 209 | import Vue from 'vue' 210 | import Vuex from 'vuex' 211 | 212 | Vue.use(Vuex); 213 | 214 | const state = { 215 | currentArticle: {id: '1', title: '学习笔记', tag: 'vue'}, // 当前文章状态, 名字, 标签 216 | count: 1 217 | } 218 | 219 | // 使用常量替代 Mutation 事件类型 220 | const types = { 221 | CURRENT_ARTICLE: 'CURRENT_ARTICLE', 222 | COUNT: 'COUNT' 223 | } 224 | 225 | const actions = { 226 | currentArticle({commit}, obj) { 227 | commit(types.CURRENT_ARTICLE, obj); 228 | }, 229 | countIncrement({commit}, n) { 230 | commit(types.COUNT, n); 231 | } 232 | } 233 | 234 | const mutations = { 235 | [types.CURRENT_ARTICLE](state, obj) { 236 | obj.id == undefined ? false : state.currentArticle.id = obj.id; 237 | obj.title == undefined ? false : state.currentArticle.title = obj.title; 238 | obj.tag == undefined ? false : state.currentArticle.tag = obj.tag; 239 | obj.catalog == undefined ? false : state.currentArticle.catalog = obj.catalog; 240 | }, 241 | [types.COUNT](state, n = 1) { 242 | state.count += n; 243 | } 244 | } 245 | 246 | export default new Vuex.Store({ 247 | state, 248 | actions, 249 | mutations, 250 | }) 251 | ``` 252 | --- 253 | ### ES6 了解下 254 | 往下之前先了解下 ES6 的新语法 [ES6教程](http://es6.ruanyifeng.com/#docs/object) 255 | 256 | 1. 对象的解构赋值,变量必须与属性同名,才能取到正确的值 257 | ```js 258 | let { foo, bar } = { foo: "aaa", bar: "bbb" }; 259 | foo // "aaa" 260 | bar // "bbb" 261 | 262 | const actions = { 263 | currentArticle({commit}, obj) { 264 | commit(types.CURRENT_ARTICLE, obj); 265 | }, 266 | } 267 | // 等同于 268 | const actions = { 269 | currentArticle(context, obj) { 270 | context.commit(types.CURRENT_ARTICLE, obj); 271 | }, 272 | } 273 | 274 | // 传入 currentArticle 的第一个参数是一个与 store 实例具有相同方法和属性的 context 对象,可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters,这里只要用到 context.commit 这个方法,所以可以 275 | {commit} = context 即 commit = context.commit 276 | ``` 277 | 278 | 2. ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值 279 | ```js 280 | const foo = 'bar'; 281 | const baz = {foo}; 282 | baz // {foo: "bar"} 283 | 284 | // 等同于 285 | const baz = {foo: foo}; 286 | 287 | export default new Vuex.Store({ 288 | state, 289 | actions, 290 | mutations, 291 | }) 292 | 293 | // 等同于 294 | export default new Vuex.Store({ 295 | state: state, 296 | actions: actions, 297 | mutations: mutations 298 | }) 299 | ``` 300 | 3. 对象方法属性可以简写 301 | ```js 302 | const o = { 303 | method() { 304 | return "Hello!"; 305 | } 306 | }; 307 | 308 | // 等同于 309 | 310 | const o = { 311 | method: function() { 312 | return "Hello!"; 313 | } 314 | }; 315 | ``` 316 | 4. ES6 允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在方括号内 317 | ```js 318 | let propKey = 'foo'; 319 | 320 | let obj = { 321 | [propKey]: true, 322 | ['a' + 'bc']: 123 323 | }; 324 | ``` 325 | 所以 326 | ```js 327 | const mutations = { 328 | [types.CURRENT_ARTICLE](state, obj) { 329 | 330 | }, 331 | } 332 | // 等同于 333 | const mutations = { 334 | ['CURRENT_ARTICLE'](state, obj) { 335 | 336 | }, 337 | } 338 | // 等同于 339 | const mutations = { 340 | CURRENT_ARTICLE: function(state, obj) { 341 | 342 | }, 343 | } 344 | ``` 345 | 346 | --- 347 | ### 组件中使用 state 348 | 言归正传,我们定义一个 state 状态属性 currentArticle 对象,用来记录当前文章的 id,title,tags,catalog,还是使用 Blog.vue 和 LeftNavItem.vue 来测试 349 | ```js 350 | // Blog.vue 351 | 364 | 365 | 391 | 392 | 402 | ``` 403 | 我们可以在每个组件内引入 store 404 | ```js 405 | import store from '../../store/index' 406 | ``` 407 | 然后通过计算属性(命名符合规则即可)来获取状态值,可以看到,要获得多个状态时,将这些状态都声明为计算属性会有些重复和冗余,内置 mapState 辅助函数可以帮助我们生产计算属性。要想使用辅助函数,需要先将 store 实例注册到 vue 实例中,这样 store 实例会注入到跟组件下的所有组件,且子组件能通过 this.$store 访问到。 408 | 409 | 大家 src/main.js 引入 store 实例,并在 vue 根实例中注册 store 选项 410 | ```js 411 | import Vue from 'vue' 412 | import App from './App' 413 | import router from './router' 414 | import ElementUI from 'element-ui' 415 | import 'element-ui/lib/theme-chalk/index.css' 416 | import axios from 'axios' 417 | import store from './store/index.js' 418 | 419 | Vue.config.productionTip = false 420 | 421 | Vue.use(ElementUI) 422 | Vue.prototype.$http = axios 423 | 424 | /* eslint-disable no-new */ 425 | new Vue({ 426 | el: '#app', 427 | router, 428 | store, 429 | components: { App }, 430 | template: '' 431 | }) 432 | ``` 433 | 改写 src/components/page/Blog.vue 使用 mapState 辅助函数生产状态属性 434 | ```js 435 | 448 | 449 | 468 | 469 | 562 | ``` 563 | 点击 + 号后调用方法 countAdd 触发 countIncrement 的 Action,接着触发类型为 COUNT 的 mutation,完成状态的修改。根据 action 中 countIncrement 方法的定义,可以传入第二个参数,this.$store.dispatch('countIncrement', 3)。 564 | 565 | ### 辅助函数 mapAction 566 | Action 也有辅助函数 mapAction 将组件的 methods 映射为 store.dispatch(需要现在根结点注入 store) 567 | ```js 568 | 582 | 583 | 605 | 606 | 616 | ``` 617 | 和 mapStata 类似,mapActions 也可以传入对象,使用别名来代替 countIncrement 618 | ```js 619 | methods: { 620 | countAdd() { 621 | this.add(2); 622 | }, 623 | ...mapActions({ 624 | add: 'countIncrement' 625 | }) 626 | } 627 | ``` 628 | 我个人觉得 vuex 中的 state、actions 比较难理解,所以笔记就记下这两块,其他官方文档应该可以看明白,和这两个用法也都比较类似。 629 | 630 | ## 模块化 store 631 | 对于大型项目,一般把 vuex 相关代码分割到模块中 632 | 在 src/store 下新建文件 633 | - index.js // 初始化 state,导出 Vuex.Store 实例 634 | - actions.js // acitons 对象 635 | - mutation_type.js // 常量化 mutation 类型 636 | - mutation.js // mutation 对象 637 | 638 | ```js 639 | // index.js 640 | import Vue from 'vue' 641 | import Vuex from 'vuex' 642 | import actions from './action' 643 | import mutations from './mutation' 644 | 645 | Vue.use(Vuex); 646 | 647 | const state = { 648 | currentArticle: {id: '1', title: '学习笔记', tag: 'vue'}, 649 | count: 1 650 | } 651 | 652 | export default new Vuex.Store({ 653 | state, 654 | actions, 655 | mutations, 656 | }) 657 | ``` 658 | 659 | ```js 660 | // actions.js 661 | import * as types from './mutation_type' 662 | 663 | export default { 664 | currentArticle({commit}, obj) { 665 | commit(types.CURRENT_ARTICLE, obj); 666 | }, 667 | countIncrement({commit}, n) { 668 | commit(types.COUNT, n); 669 | } 670 | } 671 | ``` 672 | 673 | ```js 674 | // mutation_type.js 675 | export const CURRENT_ARTICLE = 'CURRENT_ARTICLE' 676 | export const COUNT = 'COUNT' 677 | ``` 678 | 679 | ```js 680 | // mutation.js 681 | import * as types from './mutation_type' 682 | 683 | export default { 684 | [types.CURRENT_ARTICLE](state, obj) { 685 | obj.id == undefined ? false : state.currentArticle.id = obj.id; 686 | obj.title == undefined ? false : state.currentArticle.title = obj.title; 687 | obj.tag == undefined ? false : state.currentArticle.tag = obj.tag; 688 | obj.catalog == undefined ? false : state.currentArticle.catalog = obj.catalog; 689 | }, 690 | [types.COUNT](state, n = 1) { 691 | state.count += n; 692 | } 693 | } 694 | ``` 695 | 然后在 main.js 中注册 Store 实例即可 696 | ```js 697 | // main.js 698 | import Vue from 'vue' 699 | import App from './App' 700 | import router from './router' 701 | import store from './store/index.js' 702 | 703 | Vue.config.productionTip = false 704 | 705 | /* eslint-disable no-new */ 706 | new Vue({ 707 | el: '#app', 708 | router, 709 | store, 710 | components: { App }, 711 | template: '' 712 | }) 713 | ``` -------------------------------------------------------------------------------- /articles/vue+node+mysql搭建个人博客(一).md: -------------------------------------------------------------------------------- 1 | # 准备工作 2 | 3 | ## 安装node,这是必须的 4 | 新版node自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西。 5 | 验证是否安装成功: 6 | ![验证是否安装成功](http://otr9a8wg0.bkt.clouddn.com/cmder%E9%AA%8C%E8%AF%81node_npm_vue.jpg) 7 | 推荐windows下终端工具:[cmder](http://cmder.net/) 8 | 9 | ## npm安装vue-cli 10 | 使用npm下载依赖包是可能有些慢,所以这里可以换上淘宝的镜像cnpm。 11 | 打开终端(可以在任何位置),输入 12 | ```npm install cnpm -g --registry=https://registry.npm.taobao.org``` 13 | cnpm跟npm用法完全一致,只是在执行命令时将npm改为cnpm。 14 | 现在来安装vue-cli:输入 15 | ```npm install -g vue-cli``` 或者 ```cnpm install -g vue-cli``` 16 | 命令中 -g 表示全局安装,会安装到node安装目录下的node_modules文件夹下,看看里面是不是多了vue-cli文件夹,如果没有,看看npm模块的安装路径 17 | ```npm config ls``` 18 | 可以查看模块的安装路径 prefix,具体设置请自行百度。 19 | 20 | ![npm模块安装路径](http://otr9a8wg0.bkt.clouddn.com/npm%E6%A8%A1%E5%9D%97%E5%AE%89%E8%A3%85%E8%B7%AF%E5%BE%84.jpg) 21 | 22 | ## vue-cli快速构建项目 23 | - 选定一个你喜欢的文件夹,进入该文件夹下,之后创建的项目目录就在文件夹下 24 | - 打开终端,进入目标文件夹,以 D:\ 为例,使用webpack模板构建项目,输入 25 | ```vue init webpack my-blog``` 26 | 此时会自动从github下载文件目录到目标文件夹,上不了github的只能想办法了,从别处把构建好的文件全部拷过来也是可以的。 27 | 28 | ## 运行项目 29 | - 1、进入my-blog文件夹,首先可以看到文件夹下有一个package.json文件,这个文件很重要,里面记录的项目的一些信息和运行成功运行项目必须的一些依赖包,之后安装的一些包也要记录到里面,方便别人拷贝过来你的项目时安装依赖,顺利运行。 30 | - 2、新版本的 vue-cli 在执行 ```vue init webpack my-blog``` 第9步时会有一个选择: 31 | ![npm i for pro](http://otr9a8wg0.bkt.clouddn.com/npm%20i%20for%20pro.jpg) 32 | 如果选择了Yes,则可跳过步骤3,如果选择了No,则按照步骤3进入文件夹安装依赖。 33 | - 3、终端输入(要在此文件夹下)输入:```cnpm install``` install可以简写为 i 即 ```cnpm i```,cnpm安装应该挺快的,安装完成后会看到文件夹下多了个node_modules文件夹,里面就是运行项目所需要的一些依赖包,可以看到此文件夹虽然不大,但是里面文件个数有上千个,所以拷贝起来也是挺麻烦的,所以把依赖包记录到package.json里面,别人只要重新下载安装一下就好了,上传到github上也方便。 34 | - 4、启动项目:输入 ```npm run dev```,等待浏览器自动打开。 35 | npm run dev 执行的命令即是package.json里 scripts下的dev:node build/dev-server.js 36 | ``` 37 | "scripts": { 38 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 39 | "start": "npm run dev", 40 | "build": "node build/build.js" 41 | }, 42 | ``` 43 | ![构建项目](http://otr9a8wg0.bkt.clouddn.com/vue%20init%20webpack.jpg) 44 | 45 | 默认端口为8080,若此时8080端口被占用则会出错 46 | ``` 47 | ... 48 | > Starting dev server... 49 | events.js:160 50 | throw er; // Unhandled 'error' event 51 | ^ 52 | Error: listen EADDRINUSE :::8080 53 | ..... 54 | ``` 55 | 可以在D:\\my-blog\\config\\index.js里修改端口 56 | ``` 57 | dev: { 58 | 59 | // Paths 60 | assetsSubDirectory: 'static', 61 | assetsPublicPath: '/', 62 | proxyTable: {}, 63 | 64 | // Various Dev Server settings 65 | host: 'localhost', // can be overwritten by process.env.HOST 66 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 67 | autoOpenBrowser: false, 68 | errorOverlay: true, 69 | notifyOnErrors: true, 70 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 71 | 72 | // https://webpack.js.org/configuration/devtool/#development 73 | devtool: 'cheap-module-eval-source-map', 74 | 75 | // If you have problems debugging vue-files in devtools, 76 | // set this to false - it *may* help 77 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 78 | cacheBusting: true, 79 | 80 | cssSourceMap: true 81 | }, 82 | ``` 83 | 启动成功后: 84 | ``` 85 | ... 86 | DONE Compiled successfully in 2597ms 87 | I Your application is running here: http://localhost:8080 88 | ``` 89 | ![启动成功](http://otr9a8wg0.bkt.clouddn.com/17-7-28/73659113.jpg) 90 | 91 | 92 | # 安装需要用到的包 93 | 首先安装项目要用到的一些组件,也可以之后遇到什么需要的再安装 94 | - element-ui:饿了么前段组件库,可以帮助快速建立起前段页面,少些很多样式 95 | - vuex:vue状态管理 96 | - axios:基于Promise 用于浏览器和 nodejs 的 HTTP 客户端 97 | - mysql:连接mysql数据库 98 | - express: 99 | - body-parser: 100 | - node-sass:sass-loader依赖 101 | - sass-loader:解析sass/scss文件 102 | 103 | 可以依次安装( npm 安装很慢的可以使用 cnpm ): 104 | ``` 105 | npm install element-ui --save (回车) 106 | npm install vuex --save (回车) 107 | npm install axios --save (回车) 108 | npm install mysql --save (回车) 109 | npm install express --save (回车) 110 | npm install body-parser --save (回车) 111 | npm install node-sass --save-dev (回车) 112 | npm install sass-loader --save-dev (回车) 113 | ``` 114 | 也可以一起安装: 115 | ``` 116 | npm install element-ui vuex axios mysql express body-parser --save (回车) 117 | npm install node-sass sass-loader --save-dev (回车) 118 | ``` 119 | --save 意思就是将依赖记录在 package.json 里的 dependencies 下,之后生产环境也是需要这些包的,--sava-dev 是将依赖记录在 package.json 里的 devDependencies 下,只是开发环境需要这些包,方便开发调试,而生产环境不需要。(-S 是 --save 的缩写,-D 是 --save-dev 的缩写) 120 | ``` 121 | "dependencies": { 122 | "axios": "^0.18.0", 123 | "body-parser": "^1.18.3", 124 | "element-ui": "^2.3.9", 125 | "express": "^4.16.3", 126 | "mysql": "^2.15.0", 127 | "vue": "^2.5.2", // 项目构建完就有了 128 | "vue-router": "^3.0.1", // 项目构建完就有了 当时"Install vue-router"选了Yes 129 | "vuex": "^3.0.1" 130 | }, 131 | "devDependencies": { 132 | ... 133 | "node-sass": "^4.9.0", 134 | "sass-loader": "^7.0.1", 135 | ... 136 | }, 137 | ``` 138 | 139 | ``` 140 | 使用scss/sass前必须先安装node-sass、sass-loader,否则运行npm run dev时会报错 141 | 147 | ``` 148 | 149 | # 调用后台接口 ajax 请求数据 150 | 151 | 1、打开入口js文件main.js,引入element-ui组件来搭建页面 [element-ui 查看官网文档](http://element.eleme.io/#/zh-CN/component/installation)。 152 | ``` 153 | // The Vue build version to load with the `import` command 154 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 155 | import Vue from 'vue' 156 | import App from './App' 157 | import router from './router' 158 | import axios from 'axios' 159 | import ElementUI from 'element-ui' 160 | import 'element-ui/lib/theme-chalk/index.css' 161 | 162 | Vue.config.productionTip = false 163 | Vue.use(ElementUI); 164 | Vue.prototype.$http = axios; 165 | 166 | /* eslint-disable no-new */ 167 | new Vue({ 168 | el: '#app', 169 | router, 170 | components: { App }, 171 | template: '' 172 | }) 173 | ``` 174 | 其中 axios 用来完成 ajax 请求, 175 | ``` 176 | import axios from 'axios' 177 | axios.get('/', function() {}); 178 | axios.post('/', function() {}); 179 | 180 | // 将 axios 添加的 Vue 原型上后就不需要再在每个需要使用它的页面引入了 181 | Vue.prototype.$http = axios; 182 | $http.get('/', function() {}); 183 | $http.post('/', function() {}); 184 | ``` 185 | 186 | 2、每个页面都相当于一个组件,文件以.vue结尾,第一次启动成功时看到的页面就是组件Hello.vue,路径src/components/Hello.vue。路由地址在 src/router/index.js 中配置,打开修改我们待会自己要用的: 187 | ``` 188 | import Vue from 'vue' 189 | import Router from 'vue-router' 190 | import HelloWorld from '@/components/HelloWorld' 191 | import Home from '@/components/pages/Home' 192 | import Blog from '@/components/pages/Blog' 193 | 194 | Vue.use(Router) 195 | 196 | export default new Router({ 197 | routes: [ 198 | // { 199 | // path: '/', 200 | // name: 'HelloWorld', 201 | // component: HelloWorld 202 | // }, 203 | { 204 | path: '/', // http://localhost:8080/#/ 205 | name: 'Home', 206 | component: Home 207 | }, 208 | { 209 | path: '/blog', // http://localhost:8080/#/blog 210 | name: 'Blog', 211 | component: Blog 212 | } 213 | ] 214 | }) 215 | ``` 216 | 上面引入了三个组件HelloWorld.vue(默认),Home.vue,Blog.vue。 217 | path是页面地址,name可以随便写,component 是 import 的组件名。 218 | 219 | 3、在 scr/components 下新建文件夹 pages,在 pages 下新建文件 Home.vue,Blog.vue,里面按规则要求写好内容,运行工程打开页面 http://localhost:8080/#/、http://localhost:8080/#/blog 即可看到相应的内容。 220 | 在 Blog.vue 中输入下面内容用来后面测试调用接口 221 | ``` 222 | 228 | 229 | 248 | ``` 249 | 4、后端使用Express做服务端提供数据接口,不了解的可以先去官网文档大致了解一下 [Express官网](http://expressjs.com/en/starter/installing.html),在根目录my-blog下创建文件夹server用来存放后端数据库配置数据和相关方法api。 250 | server文件夹下创建文件:index.js 251 | ``` 252 | const path = require('path'); 253 | const express = require('express'); 254 | const app = express(); 255 | 256 | app.get('/api/getArticle', (req, res, next) => { 257 | res.json({ 258 | data: '后台返回结果 getArticle' 259 | }) 260 | }) 261 | 262 | // 监听端口 263 | app.listen(3000); 264 | console.log('success listen at port:3000......'); 265 | ``` 266 | 另开一个 CMD 窗口,进入目录 D:\my-blog\server 267 | ``` 268 | D:\my-blog\server 269 | $ node index.js 270 | success listen at port:3000...... 271 | ``` 272 | 5、打开 http://localhost:8080/#/blog 点击按钮"调用后台接口",会发现控制台报错 273 | ![调后台接口报错_跨域](http://otr9a8wg0.bkt.clouddn.com/%E8%B0%83%E5%90%8E%E5%8F%B0%E6%8E%A5%E5%8F%A3%E6%8A%A5%E9%94%99_%E8%B7%A8%E5%9F%9F.jpg) 274 | 这是因为我们工程运行的端口是8080,而后端程序运行的端口是3000,所以是跨域请求,要想请求成功,就要先在配置里设置一下代理 275 | 276 | 6、打开文件 /config/index.js,将 proxyTable 项设置如下 277 | ``` 278 | proxyTable: { 279 | '/api': { 280 | target: 'http://localhost:3000/api', 281 | changeOrigin: true, 282 | pathRewrite: { 283 | '^/api': '' 284 | } 285 | } 286 | } 287 | 288 | ``` 289 | - '/api': 表示所有以 /api 为开头的请求,如我们的请求 this.$http.get('/api/getArticle') 290 | - target: 将所有以 /api 为开头请求转发到 http://localhost:3000/api 291 | - changeOrigin: true/false, Default: false,本地会虚拟一个服务端接收你的请求并代你发送该请求(不太明白,false 试了也可以) 292 | - pathRewrite: 重写地址。 '^/api': '' 表示将以 /api 开头的请求的地址中的 '/api' 替换为 '', 293 | 即 path = path.replace(/^\/api/, '') 294 | eg: this.\$http.get('/api/getArticle') 295 | path = '/api/getArticle' 296 | path = path.replace(/^\/api/, '') = '/getArticle' 297 | 这样目标请求就变成了 http://localhost:3000/api/getArticle , 298 | 如果不写 pathRewrite, 请求则为 http://localhost:3000/api/api/getArticle 所以也可以这样 299 | ``` 300 | proxyTable: { 301 | '/api': { 302 | target: 'http://localhost:3000', 303 | changeOrigin: true, 304 | } 305 | } 306 | ``` 307 | 最后请求同样转发为 http://localhost:3000/api/getArticle , 总之要和后台的接口路径对应上,不过还是建议加上 pathRewrite,方便同类方法调用 308 | ``` 309 | // server/index.js 310 | const path = require('path'); 311 | const express = require('express'); 312 | const router = express.Router(); 313 | const app = express(); 314 | 315 | app.use('/add', router); 316 | app.use('/del', router); 317 | 318 | router.get('/getArticle1', (req, res, next) => { 319 | api.getArticle(req, res, next); 320 | }) 321 | router.get('/getArticle2', (req, res, next) => { 322 | api.getArticle(req, res, next); 323 | }) 324 | 325 | router.get('/delArticle1', (req, res, next) => { 326 | api.getArticle(req, res, next); 327 | }) 328 | router.get('/delArticle2', (req, res, next) => { 329 | api.getArticle(req, res, next); 330 | }) 331 | 332 | // 监听端口 333 | app.listen(3000); 334 | console.log('success listen at port:3000......'); 335 | 336 | ---------------------------------------------- 337 | 338 | // congif/index.js 339 | proxyTable: { 340 | '/add': { 341 | target: 'http://localhost:3000/add', 342 | changeOrigin: true, 343 | pathRewrite: { 344 | '^/add': '' 345 | } 346 | }, 347 | '/del': { 348 | target: 'http://localhost:3000/del', 349 | changeOrigin: true, 350 | pathRewrite: { 351 | '^/del': '' 352 | } 353 | }, 354 | }, 355 | ``` 356 | 357 | 7、正确返回数据 358 | ![](http://otr9a8wg0.bkt.clouddn.com/%E5%90%8E%E5%8F%B0%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C.jpg) 359 | 360 | 361 | # 数据库存取数据(Mysql) 362 | Mysql可视化工具我用的是Navicat For Mysql,新建连接,数据库,数据表,查询等都可在其中完成,当然熟悉命令的都可以在cmd中命令完成 363 | ### Mysql 新建连接 364 | ![新建数据库](http://otr9a8wg0.bkt.clouddn.com/%E6%96%B0%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93Mysql.jpg) 365 | ### 连接数据库 366 | 在 src/server 下新建文件 db.js,写入下面代码 367 | ```js 368 | const mysql = require('mysql'); 369 | 370 | const mysqlConfig = { 371 | host: 'localhost', // 新建数据库连接时的 主机名或ID地址 内容 372 | user: 'root', 373 | password: '8023', // root 密码 374 | database: 'myBlog', // 数据库名 375 | port: '3306' 376 | } 377 | const pool = mysql.createPool({ 378 | host: mysqlConfig.host, 379 | user: mysqlConfig.user, 380 | password: mysqlConfig.password, 381 | database: mysqlConfig.database, 382 | port: mysqlConfig.port, 383 | multipleStatements: true // 多语句查询 384 | }); 385 | 386 | var setValue = function() { 387 | pool.getConnection((err, connection) => { 388 | var sql = 'INSERT INTO test(id, name) VALUES (1, "blog")' 389 | connection.query(sql, (err, result) => { 390 | console.log(result); 391 | connection.release(); 392 | }) 393 | }) 394 | } 395 | 396 | setValue(); 397 | ``` 398 | 引入包 mysql,创建连接池 mysql.createPool,sql语法和在命令中使用的形同,拼成字符串即可,在 server 目录下运行 db.js 文件,刷新数据库 399 | ![添加数据](http://otr9a8wg0.bkt.clouddn.com/dbSetValue.jpg) 400 | 401 | 同理可增删查改数据 402 | ```js 403 | // 查询数据,? 的值填入 connection.jquery 的第二个参数(数组)中 404 | // WHERE id = ? AND name = ? ---> connetion.query(sql, [1, "blog"], () => ) 405 | var getValue = function() { 406 | pool.getConnection((err, connection) => { 407 | var sql = 'SELECT * FROM test WHERE id = ?' 408 | connection.query(sql, [1], (err, result) => { 409 | console.log(result); 410 | connection.release(); 411 | }) 412 | }) 413 | } 414 | getValue(); 415 | 416 | /* 417 | $ node db.js 418 | [ RowDataPacket { id: '1', name: 'blog' } ] 419 | */ 420 | 421 | // 更新数据 422 | var updValue = function() { 423 | pool.getConnection((err, connection) => { 424 | var sql = 'UPDATE test SET name = ? WHERE id = ?' 425 | connection.query(sql, [22, 1], (err, result) => { 426 | console.log(result); 427 | connection.release(); 428 | }) 429 | }) 430 | } 431 | updValue(); 432 | 433 | // 删除数据 434 | var delValue = function() { 435 | pool.getConnection((err, connection) => { 436 | var sql = 'DELETE FROM test WHERE id = ?' 437 | connection.query(sql, [1], (err, result) => { 438 | console.log(result); 439 | connection.release(); 440 | }) 441 | }) 442 | } 443 | delValue(); 444 | ``` 445 | 结合前面的 ajax 请求数据,我们便可以轻松的对数据库中的数据进行操作了,下面来模块化这些操作。 446 | ### 模块化后端代码 447 | 在 /server 下创建文件 448 | - db.js 数据库连接配置 449 | - api.js 连接数据库,各种方法实现 450 | - sqlMap.js sql语句 451 | - router.js 后端 express 路由配置 452 | - index.js 后端入口文件,启动后端服务 453 | 454 | 1、db.js 455 | ```js 456 | // 数据库连接配置 457 | module.exports = { 458 | mysql: { 459 | host: 'localhost', // 新建数据库连接时的 主机名或ID地址 内容 460 | user: 'root', 461 | password: '8023', // root 密码 462 | database: 'myBlog', // 数据库名 463 | port: '3306' 464 | } 465 | } 466 | ``` 467 | 2、api.js 468 | ```js 469 | const mysql = require('mysql'); 470 | const dbConfig = require('./db'); 471 | const sqlMap = require('./sqlMap'); 472 | 473 | const pool = mysql.createPool({ 474 | host: dbConfig.mysql.host, 475 | user: dbConfig.mysql.user, 476 | password: dbConfig.mysql.password, 477 | database: dbConfig.mysql.database, 478 | port: dbConfig.mysql.port, 479 | multipleStatements: true // 多语句查询 480 | }); 481 | 482 | module.exports = { 483 | getValue(req, res, next) { 484 | var id = req.query.id; 485 | pool.getConnection((err, connection) => { 486 | var sql = sqlMap.getValue; 487 | connection.query(sql, [id], (err, result) => { 488 | res.json(result); 489 | connection.release(); 490 | }) 491 | }) 492 | }, 493 | setValue(req, res, next) { 494 | console.log(req.body); 495 | var id = req.body.id, name = req.body.name; 496 | pool.getConnection((err, connection) => { 497 | var sql = sqlMap.setValue; 498 | connection.query(sql, [name, id], (err, result) => { 499 | res.json(result); 500 | connection.release(); 501 | }) 502 | }) 503 | } 504 | } 505 | 506 | ``` 507 | 3、sqlMap.js 508 | ```js 509 | var sqlMap = { 510 | getValue: 'SELECT * FROM test WHERE id = ?', 511 | setValue: 'UPDATE test SET name = ? WHERE id = ?' 512 | } 513 | 514 | module.exports = sqlMap; 515 | ``` 516 | 4、router.js 517 | ```js 518 | const express = require('express'); 519 | const router = express.Router(); 520 | const api = require('./api'); 521 | 522 | router.get('/getValue', (req, res, next) => { 523 | api.getValue(req, res, next); 524 | }); 525 | 526 | router.post('/setValue', (req, res, next) => { 527 | api.setValue(req, res, next); 528 | }); 529 | 530 | module.exports = router; 531 | ``` 532 | 5、index.js 533 | ```js 534 | const routerApi = require('./router'); 535 | const bodyParser = require('body-parser'); // post 数据是需要 536 | const express = require('express'); 537 | const app = express(); 538 | 539 | app.use(bodyParser.json()); 540 | 541 | // 后端api路由 542 | app.use('/api', routerApi); 543 | 544 | // 监听端口 545 | app.listen(3000); 546 | console.log('success listen at port:3000......'); 547 | ``` 548 | 549 | 在 /scr/components/pages/Blog.vue 文件中写入下面代码测试 550 | ```js 551 | 558 | 559 | 588 | ``` 589 | - get:第二个参数(可选)是一个对象,以 params 为属性,将条件数据传到后台,后台通过 req.query 可以获得 params 对应的值 590 | - post:第二个参数(可选)也是一个对象,属性任意,将提交数据传到后台,后台通过 req.body 可以获得这个对象,req.body 数据的解析需要用到包 body-parser,在 index.js 中引入 use 即可。 591 | 592 | 打开两个命令窗口分别运行工程,运行后端服务,即可进行测试: 593 | ```js 594 | D:\my-blog 595 | $ npm run dev 596 | ``` 597 | ```js 598 | D:\my-blog\server 599 | $ node index.js 600 | ``` -------------------------------------------------------------------------------- /articles/5d2c3ebf6345ec09e0d99d35_555.md: -------------------------------------------------------------------------------- 1 | # 准备工作 2 | 3 | ## 安装node,这是必须的 4 | 新版node自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西。 5 | 验证是否安装成功: 6 | ![验证是否安装成功](http://otr9a8wg0.bkt.clouddn.com/cmder%E9%AA%8C%E8%AF%81node_npm_vue.jpg) 7 | 推荐windows下终端工具:[cmder](http://cmder.net/) 8 | 9 | ## npm安装vue-cli 10 | 使用npm下载依赖包是可能有些慢,所以这里可以换上淘宝的镜像cnpm。 11 | 打开终端(可以在任何位置),输入 12 | ```npm install cnpm -g --registry=https://registry.npm.taobao.org``` 13 | cnpm跟npm用法完全一致,只是在执行命令时将npm改为cnpm。 14 | 现在来安装vue-cli:输入 15 | ```npm install -g vue-cli``` 或者 ```cnpm install -g vue-cli``` 16 | 命令中 -g 表示全局安装,会安装到node安装目录下的node_modules文件夹下,看看里面是不是多了vue-cli文件夹,如果没有,看看npm模块的安装路径 17 | ```npm config ls``` 18 | 可以查看模块的安装路径 prefix,具体设置请自行百度。 19 | 20 | ![npm模块安装路径](http://otr9a8wg0.bkt.clouddn.com/npm%E6%A8%A1%E5%9D%97%E5%AE%89%E8%A3%85%E8%B7%AF%E5%BE%84.jpg) 21 | 22 | ## vue-cli快速构建项目 23 | - 选定一个你喜欢的文件夹,进入该文件夹下,之后创建的项目目录就在文件夹下 24 | - 打开终端,进入目标文件夹,以 D:\ 为例,使用webpack模板构建项目,输入 25 | ```vue init webpack my-blog``` 26 | 此时会自动从github下载文件目录到目标文件夹,上不了github的只能想办法了,从别处把构建好的文件全部拷过来也是可以的。 27 | 28 | ## 运行项目 29 | - 1、进入my-blog文件夹,首先可以看到文件夹下有一个package.json文件,这个文件很重要,里面记录的项目的一些信息和运行成功运行项目必须的一些依赖包,之后安装的一些包也要记录到里面,方便别人拷贝过来你的项目时安装依赖,顺利运行。 30 | - 2、新版本的 vue-cli 在执行 ```vue init webpack my-blog``` 第9步时会有一个选择: 31 | ![npm i for pro](http://otr9a8wg0.bkt.clouddn.com/npm%20i%20for%20pro.jpg) 32 | 如果选择了Yes,则可跳过步骤3,如果选择了No,则按照步骤3进入文件夹安装依赖。 33 | - 3、终端输入(要在此文件夹下)输入:```cnpm install``` install可以简写为 i 即 ```cnpm i```,cnpm安装应该挺快的,安装完成后会看到文件夹下多了个node_modules文件夹,里面就是运行项目所需要的一些依赖包,可以看到此文件夹虽然不大,但是里面文件个数有上千个,所以拷贝起来也是挺麻烦的,所以把依赖包记录到package.json里面,别人只要重新下载安装一下就好了,上传到github上也方便。 34 | - 4、启动项目:输入 ```npm run dev```,等待浏览器自动打开。 35 | npm run dev 执行的命令即是package.json里 scripts下的dev:node build/dev-server.js 36 | ``` 37 | "scripts": { 38 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 39 | "start": "npm run dev", 40 | "build": "node build/build.js" 41 | }, 42 | ``` 43 | ![构建项目](http://otr9a8wg0.bkt.clouddn.com/vue%20init%20webpack.jpg) 44 | 45 | 默认端口为8080,若此时8080端口被占用则会出错 46 | ``` 47 | ... 48 | > Starting dev server... 49 | events.js:160 50 | throw er; // Unhandled 'error' event 51 | ^ 52 | Error: listen EADDRINUSE :::8080 53 | ..... 54 | ``` 55 | 可以在D:\\my-blog\\config\\index.js里修改端口 56 | ``` 57 | dev: { 58 | 59 | // Paths 60 | assetsSubDirectory: 'static', 61 | assetsPublicPath: '/', 62 | proxyTable: {}, 63 | 64 | // Various Dev Server settings 65 | host: 'localhost', // can be overwritten by process.env.HOST 66 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 67 | autoOpenBrowser: false, 68 | errorOverlay: true, 69 | notifyOnErrors: true, 70 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 71 | 72 | // https://webpack.js.org/configuration/devtool/#development 73 | devtool: 'cheap-module-eval-source-map', 74 | 75 | // If you have problems debugging vue-files in devtools, 76 | // set this to false - it *may* help 77 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 78 | cacheBusting: true, 79 | 80 | cssSourceMap: true 81 | }, 82 | ``` 83 | 启动成功后: 84 | ``` 85 | ... 86 | DONE Compiled successfully in 2597ms 87 | I Your application is running here: http://localhost:8080 88 | ``` 89 | ![启动成功](http://otr9a8wg0.bkt.clouddn.com/17-7-28/73659113.jpg) 90 | 91 | 92 | # 安装需要用到的包 93 | 首先安装项目要用到的一些组件,也可以之后遇到什么需要的再安装 94 | - element-ui:饿了么前段组件库,可以帮助快速建立起前段页面,少些很多样式 95 | - vuex:vue状态管理 96 | - axios:基于Promise 用于浏览器和 nodejs 的 HTTP 客户端 97 | - mysql:连接mysql数据库 98 | - express: 99 | - body-parser: 100 | - node-sass:sass-loader依赖 101 | - sass-loader:解析sass/scss文件 102 | 103 | 可以依次安装( npm 安装很慢的可以使用 cnpm ): 104 | ``` 105 | npm install element-ui --save (回车) 106 | npm install vuex --save (回车) 107 | npm install axios --save (回车) 108 | npm install mysql --save (回车) 109 | npm install express --save (回车) 110 | npm install body-parser --save (回车) 111 | npm install node-sass --save-dev (回车) 112 | npm install sass-loader --save-dev (回车) 113 | ``` 114 | 也可以一起安装: 115 | ``` 116 | npm install element-ui vuex axios mysql express body-parser --save (回车) 117 | npm install node-sass sass-loader --save-dev (回车) 118 | ``` 119 | --save 意思就是将依赖记录在 package.json 里的 dependencies 下,之后生产环境也是需要这些包的,--sava-dev 是将依赖记录在 package.json 里的 devDependencies 下,只是开发环境需要这些包,方便开发调试,而生产环境不需要。(-S 是 --save 的缩写,-D 是 --save-dev 的缩写) 120 | ``` 121 | "dependencies": { 122 | "axios": "^0.18.0", 123 | "body-parser": "^1.18.3", 124 | "element-ui": "^2.3.9", 125 | "express": "^4.16.3", 126 | "mysql": "^2.15.0", 127 | "vue": "^2.5.2", // 项目构建完就有了 128 | "vue-router": "^3.0.1", // 项目构建完就有了 当时"Install vue-router"选了Yes 129 | "vuex": "^3.0.1" 130 | }, 131 | "devDependencies": { 132 | ... 133 | "node-sass": "^4.9.0", 134 | "sass-loader": "^7.0.1", 135 | ... 136 | }, 137 | ``` 138 | 139 | ``` 140 | 使用scss/sass前必须先安装node-sass、sass-loader,否则运行npm run dev时会报错 141 | 147 | ``` 148 | 149 | # 调用后台接口 ajax 请求数据 150 | 151 | 1、打开入口js文件main.js,引入element-ui组件来搭建页面 [element-ui 查看官网文档](http://element.eleme.io/#/zh-CN/component/installation)。 152 | ``` 153 | // The Vue build version to load with the `import` command 154 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 155 | import Vue from 'vue' 156 | import App from './App' 157 | import router from './router' 158 | import axios from 'axios' 159 | import ElementUI from 'element-ui' 160 | import 'element-ui/lib/theme-chalk/index.css' 161 | 162 | Vue.config.productionTip = false 163 | Vue.use(ElementUI); 164 | Vue.prototype.$http = axios; 165 | 166 | /* eslint-disable no-new */ 167 | new Vue({ 168 | el: '#app', 169 | router, 170 | components: { App }, 171 | template: '' 172 | }) 173 | ``` 174 | 其中 axios 用来完成 ajax 请求, 175 | ``` 176 | import axios from 'axios' 177 | axios.get('/', function() {}); 178 | axios.post('/', function() {}); 179 | 180 | // 将 axios 添加的 Vue 原型上后就不需要再在每个需要使用它的页面引入了 181 | Vue.prototype.$http = axios; 182 | $http.get('/', function() {}); 183 | $http.post('/', function() {}); 184 | ``` 185 | 186 | 2、每个页面都相当于一个组件,文件以.vue结尾,第一次启动成功时看到的页面就是组件Hello.vue,路径src/components/Hello.vue。路由地址在 src/router/index.js 中配置,打开修改我们待会自己要用的: 187 | ``` 188 | import Vue from 'vue' 189 | import Router from 'vue-router' 190 | import HelloWorld from '@/components/HelloWorld' 191 | import Home from '@/components/pages/Home' 192 | import Blog from '@/components/pages/Blog' 193 | 194 | Vue.use(Router) 195 | 196 | export default new Router({ 197 | routes: [ 198 | // { 199 | // path: '/', 200 | // name: 'HelloWorld', 201 | // component: HelloWorld 202 | // }, 203 | { 204 | path: '/', // http://localhost:8080/#/ 205 | name: 'Home', 206 | component: Home 207 | }, 208 | { 209 | path: '/blog', // http://localhost:8080/#/blog 210 | name: 'Blog', 211 | component: Blog 212 | } 213 | ] 214 | }) 215 | ``` 216 | 上面引入了三个组件HelloWorld.vue(默认),Home.vue,Blog.vue。 217 | path是页面地址,name可以随便写,component 是 import 的组件名。 218 | 219 | 3、在 scr/components 下新建文件夹 pages,在 pages 下新建文件 Home.vue,Blog.vue,里面按规则要求写好内容,运行工程打开页面 http://localhost:8080/#/、http://localhost:8080/#/blog 即可看到相应的内容。 220 | 在 Blog.vue 中输入下面内容用来后面测试调用接口 221 | ``` 222 | 228 | 229 | 248 | ``` 249 | 4、后端使用Express做服务端提供数据接口,不了解的可以先去官网文档大致了解一下 [Express官网](http://expressjs.com/en/starter/installing.html),在根目录my-blog下创建文件夹server用来存放后端数据库配置数据和相关方法api。 250 | server文件夹下创建文件:index.js 251 | ``` 252 | const path = require('path'); 253 | const express = require('express'); 254 | const app = express(); 255 | 256 | app.get('/api/getArticle', (req, res, next) => { 257 | res.json({ 258 | data: '后台返回结果 getArticle' 259 | }) 260 | }) 261 | 262 | // 监听端口 263 | app.listen(3000); 264 | console.log('success listen at port:3000......'); 265 | ``` 266 | 另开一个 CMD 窗口,进入目录 D:\my-blog\server 267 | ``` 268 | D:\my-blog\server 269 | $ node index.js 270 | success listen at port:3000...... 271 | ``` 272 | 5、打开 http://localhost:8080/#/blog 点击按钮"调用后台接口",会发现控制台报错 273 | ![调后台接口报错_跨域](http://otr9a8wg0.bkt.clouddn.com/%E8%B0%83%E5%90%8E%E5%8F%B0%E6%8E%A5%E5%8F%A3%E6%8A%A5%E9%94%99_%E8%B7%A8%E5%9F%9F.jpg) 274 | 这是因为我们工程运行的端口是8080,而后端程序运行的端口是3000,所以是跨域请求,要想请求成功,就要先在配置里设置一下代理 275 | 276 | 6、打开文件 /config/index.js,将 proxyTable 项设置如下 277 | ``` 278 | proxyTable: { 279 | '/api': { 280 | target: 'http://localhost:3000/api', 281 | changeOrigin: true, 282 | pathRewrite: { 283 | '^/api': '' 284 | } 285 | } 286 | } 287 | 288 | ``` 289 | - '/api': 表示所有以 /api 为开头的请求,如我们的请求 this.$http.get('/api/getArticle') 290 | - target: 将所有以 /api 为开头请求转发到 http://localhost:3000/api 291 | - changeOrigin: true/false, Default: false,本地会虚拟一个服务端接收你的请求并代你发送该请求(不太明白,false 试了也可以) 292 | - pathRewrite: 重写地址。 '^/api': '' 表示将以 /api 开头的请求的地址中的 '/api' 替换为 '', 293 | 即 path = path.replace(/^\/api/, '') 294 | eg: this.\$http.get('/api/getArticle') 295 | path = '/api/getArticle' 296 | path = path.replace(/^\/api/, '') = '/getArticle' 297 | 这样目标请求就变成了 http://localhost:3000/api/getArticle , 298 | 如果不写 pathRewrite, 请求则为 http://localhost:3000/api/api/getArticle 所以也可以这样 299 | ``` 300 | proxyTable: { 301 | '/api': { 302 | target: 'http://localhost:3000', 303 | changeOrigin: true, 304 | } 305 | } 306 | ``` 307 | 最后请求同样转发为 http://localhost:3000/api/getArticle , 总之要和后台的接口路径对应上,不过还是建议加上 pathRewrite,方便同类方法调用 308 | ``` 309 | // server/index.js 310 | const path = require('path'); 311 | const express = require('express'); 312 | const router = express.Router(); 313 | const app = express(); 314 | 315 | app.use('/add', router); 316 | app.use('/del', router); 317 | 318 | router.get('/getArticle1', (req, res, next) => { 319 | api.getArticle(req, res, next); 320 | }) 321 | router.get('/getArticle2', (req, res, next) => { 322 | api.getArticle(req, res, next); 323 | }) 324 | 325 | router.get('/delArticle1', (req, res, next) => { 326 | api.getArticle(req, res, next); 327 | }) 328 | router.get('/delArticle2', (req, res, next) => { 329 | api.getArticle(req, res, next); 330 | }) 331 | 332 | // 监听端口 333 | app.listen(3000); 334 | console.log('success listen at port:3000......'); 335 | 336 | ---------------------------------------------- 337 | 338 | // congif/index.js 339 | proxyTable: { 340 | '/add': { 341 | target: 'http://localhost:3000/add', 342 | changeOrigin: true, 343 | pathRewrite: { 344 | '^/add': '' 345 | } 346 | }, 347 | '/del': { 348 | target: 'http://localhost:3000/del', 349 | changeOrigin: true, 350 | pathRewrite: { 351 | '^/del': '' 352 | } 353 | }, 354 | }, 355 | ``` 356 | 357 | 7、正确返回数据 358 | ![](http://otr9a8wg0.bkt.clouddn.com/%E5%90%8E%E5%8F%B0%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C.jpg) 359 | 360 | 361 | # 数据库存取数据(Mysql) 362 | Mysql可视化工具我用的是Navicat For Mysql,新建连接,数据库,数据表,查询等都可在其中完成,当然熟悉命令的都可以在cmd中命令完成 363 | ### Mysql 新建连接 364 | ![新建数据库](http://otr9a8wg0.bkt.clouddn.com/%E6%96%B0%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93Mysql.jpg) 365 | ### 连接数据库 366 | 在 src/server 下新建文件 db.js,写入下面代码 367 | ```js 368 | const mysql = require('mysql'); 369 | 370 | const mysqlConfig = { 371 | host: 'localhost', // 新建数据库连接时的 主机名或ID地址 内容 372 | user: 'root', 373 | password: '8023', // root 密码 374 | database: 'myBlog', // 数据库名 375 | port: '3306' 376 | } 377 | const pool = mysql.createPool({ 378 | host: mysqlConfig.host, 379 | user: mysqlConfig.user, 380 | password: mysqlConfig.password, 381 | database: mysqlConfig.database, 382 | port: mysqlConfig.port, 383 | multipleStatements: true // 多语句查询 384 | }); 385 | 386 | var setValue = function() { 387 | pool.getConnection((err, connection) => { 388 | var sql = 'INSERT INTO test(id, name) VALUES (1, "blog")' 389 | connection.query(sql, (err, result) => { 390 | console.log(result); 391 | connection.release(); 392 | }) 393 | }) 394 | } 395 | 396 | setValue(); 397 | ``` 398 | 引入包 mysql,创建连接池 mysql.createPool,sql语法和在命令中使用的形同,拼成字符串即可,在 server 目录下运行 db.js 文件,刷新数据库 399 | ![添加数据](http://otr9a8wg0.bkt.clouddn.com/dbSetValue.jpg) 400 | 401 | 同理可增删查改数据 402 | ```js 403 | // 查询数据,? 的值填入 connection.jquery 的第二个参数(数组)中 404 | // WHERE id = ? AND name = ? ---> connetion.query(sql, [1, "blog"], () => ) 405 | var getValue = function() { 406 | pool.getConnection((err, connection) => { 407 | var sql = 'SELECT * FROM test WHERE id = ?' 408 | connection.query(sql, [1], (err, result) => { 409 | console.log(result); 410 | connection.release(); 411 | }) 412 | }) 413 | } 414 | getValue(); 415 | 416 | /* 417 | $ node db.js 418 | [ RowDataPacket { id: '1', name: 'blog' } ] 419 | */ 420 | 421 | // 更新数据 422 | var updValue = function() { 423 | pool.getConnection((err, connection) => { 424 | var sql = 'UPDATE test SET name = ? WHERE id = ?' 425 | connection.query(sql, [22, 1], (err, result) => { 426 | console.log(result); 427 | connection.release(); 428 | }) 429 | }) 430 | } 431 | updValue(); 432 | 433 | // 删除数据 434 | var delValue = function() { 435 | pool.getConnection((err, connection) => { 436 | var sql = 'DELETE FROM test WHERE id = ?' 437 | connection.query(sql, [1], (err, result) => { 438 | console.log(result); 439 | connection.release(); 440 | }) 441 | }) 442 | } 443 | delValue(); 444 | ``` 445 | 结合前面的 ajax 请求数据,我们便可以轻松的对数据库中的数据进行操作了,下面来模块化这些操作。 446 | ### 模块化后端代码 447 | 在 /server 下创建文件 448 | - db.js 数据库连接配置 449 | - api.js 连接数据库,各种方法实现 450 | - sqlMap.js sql语句 451 | - router.js 后端 express 路由配置 452 | - index.js 后端入口文件,启动后端服务 453 | 454 | 1、db.js 455 | ```js 456 | // 数据库连接配置 457 | module.exports = { 458 | mysql: { 459 | host: 'localhost', // 新建数据库连接时的 主机名或ID地址 内容 460 | user: 'root', 461 | password: '8023', // root 密码 462 | database: 'myBlog', // 数据库名 463 | port: '3306' 464 | } 465 | } 466 | ``` 467 | 2、api.js 468 | ```js 469 | const mysql = require('mysql'); 470 | const dbConfig = require('./db'); 471 | const sqlMap = require('./sqlMap'); 472 | 473 | const pool = mysql.createPool({ 474 | host: dbConfig.mysql.host, 475 | user: dbConfig.mysql.user, 476 | password: dbConfig.mysql.password, 477 | database: dbConfig.mysql.database, 478 | port: dbConfig.mysql.port, 479 | multipleStatements: true // 多语句查询 480 | }); 481 | 482 | module.exports = { 483 | getValue(req, res, next) { 484 | var id = req.query.id; 485 | pool.getConnection((err, connection) => { 486 | var sql = sqlMap.getValue; 487 | connection.query(sql, [id], (err, result) => { 488 | res.json(result); 489 | connection.release(); 490 | }) 491 | }) 492 | }, 493 | setValue(req, res, next) { 494 | console.log(req.body); 495 | var id = req.body.id, name = req.body.name; 496 | pool.getConnection((err, connection) => { 497 | var sql = sqlMap.setValue; 498 | connection.query(sql, [name, id], (err, result) => { 499 | res.json(result); 500 | connection.release(); 501 | }) 502 | }) 503 | } 504 | } 505 | 506 | ``` 507 | 3、sqlMap.js 508 | ```js 509 | var sqlMap = { 510 | getValue: 'SELECT * FROM test WHERE id = ?', 511 | setValue: 'UPDATE test SET name = ? WHERE id = ?' 512 | } 513 | 514 | module.exports = sqlMap; 515 | ``` 516 | 4、router.js 517 | ```js 518 | const express = require('express'); 519 | const router = express.Router(); 520 | const api = require('./api'); 521 | 522 | router.get('/getValue', (req, res, next) => { 523 | api.getValue(req, res, next); 524 | }); 525 | 526 | router.post('/setValue', (req, res, next) => { 527 | api.setValue(req, res, next); 528 | }); 529 | 530 | module.exports = router; 531 | ``` 532 | 5、index.js 533 | ```js 534 | const routerApi = require('./router'); 535 | const bodyParser = require('body-parser'); // post 数据是需要 536 | const express = require('express'); 537 | const app = express(); 538 | 539 | app.use(bodyParser.json()); 540 | 541 | // 后端api路由 542 | app.use('/api', routerApi); 543 | 544 | // 监听端口 545 | app.listen(3000); 546 | console.log('success listen at port:3000......'); 547 | ``` 548 | 549 | 在 /scr/components/pages/Blog.vue 文件中写入下面代码测试 550 | ```js 551 | 558 | 559 | 588 | ``` 589 | - get:第二个参数(可选)是一个对象,以 params 为属性,将条件数据传到后台,后台通过 req.query 可以获得 params 对应的值 590 | - post:第二个参数(可选)也是一个对象,属性任意,将提交数据传到后台,后台通过 req.body 可以获得这个对象,req.body 数据的解析需要用到包 body-parser,在 index.js 中引入 use 即可。 591 | 592 | 打开两个命令窗口分别运行工程,运行后端服务,即可进行测试: 593 | ```js 594 | D:\my-blog 595 | $ npm run dev 596 | ``` 597 | ```js 598 | D:\my-blog\server 599 | $ node index.js 600 | ``` -------------------------------------------------------------------------------- /articles/5d3aa128f6d6eb0c9404ac1d_aaaaaa.md: -------------------------------------------------------------------------------- 1 | # 准备工作 2 | 3 | ## 安装node,这是必须的 4 | 新版node自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西。 5 | 验证是否安装成功: 6 | ![验证是否安装成功](http://otr9a8wg0.bkt.clouddn.com/cmder%E9%AA%8C%E8%AF%81node_npm_vue.jpg) 7 | 推荐windows下终端工具:[cmder](http://cmder.net/) 8 | 9 | ## npm安装vue-cli 10 | 使用npm下载依赖包是可能有些慢,所以这里可以换上淘宝的镜像cnpm。 11 | 打开终端(可以在任何位置),输入 12 | ```npm install cnpm -g --registry=https://registry.npm.taobao.org``` 13 | cnpm跟npm用法完全一致,只是在执行命令时将npm改为cnpm。 14 | 现在来安装vue-cli:输入 15 | ```npm install -g vue-cli``` 或者 ```cnpm install -g vue-cli``` 16 | 命令中 -g 表示全局安装,会安装到node安装目录下的node_modules文件夹下,看看里面是不是多了vue-cli文件夹,如果没有,看看npm模块的安装路径 17 | ```npm config ls``` 18 | 可以查看模块的安装路径 prefix,具体设置请自行百度。 19 | 20 | ![npm模块安装路径](http://otr9a8wg0.bkt.clouddn.com/npm%E6%A8%A1%E5%9D%97%E5%AE%89%E8%A3%85%E8%B7%AF%E5%BE%84.jpg) 21 | 22 | ## vue-cli快速构建项目 23 | - 选定一个你喜欢的文件夹,进入该文件夹下,之后创建的项目目录就在文件夹下 24 | - 打开终端,进入目标文件夹,以 D:\ 为例,使用webpack模板构建项目,输入 25 | ```vue init webpack my-blog``` 26 | 此时会自动从github下载文件目录到目标文件夹,上不了github的只能想办法了,从别处把构建好的文件全部拷过来也是可以的。 27 | 28 | ## 运行项目 29 | - 1、进入my-blog文件夹,首先可以看到文件夹下有一个package.json文件,这个文件很重要,里面记录的项目的一些信息和运行成功运行项目必须的一些依赖包,之后安装的一些包也要记录到里面,方便别人拷贝过来你的项目时安装依赖,顺利运行。 30 | - 2、新版本的 vue-cli 在执行 ```vue init webpack my-blog``` 第9步时会有一个选择: 31 | ![npm i for pro](http://otr9a8wg0.bkt.clouddn.com/npm%20i%20for%20pro.jpg) 32 | 如果选择了Yes,则可跳过步骤3,如果选择了No,则按照步骤3进入文件夹安装依赖。 33 | - 3、终端输入(要在此文件夹下)输入:```cnpm install``` install可以简写为 i 即 ```cnpm i```,cnpm安装应该挺快的,安装完成后会看到文件夹下多了个node_modules文件夹,里面就是运行项目所需要的一些依赖包,可以看到此文件夹虽然不大,但是里面文件个数有上千个,所以拷贝起来也是挺麻烦的,所以把依赖包记录到package.json里面,别人只要重新下载安装一下就好了,上传到github上也方便。 34 | - 4、启动项目:输入 ```npm run dev```,等待浏览器自动打开。 35 | npm run dev 执行的命令即是package.json里 scripts下的dev:node build/dev-server.js 36 | ``` 37 | "scripts": { 38 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 39 | "start": "npm run dev", 40 | "build": "node build/build.js" 41 | }, 42 | ``` 43 | ![构建项目](http://otr9a8wg0.bkt.clouddn.com/vue%20init%20webpack.jpg) 44 | 45 | 默认端口为8080,若此时8080端口被占用则会出错 46 | ``` 47 | ... 48 | > Starting dev server... 49 | events.js:160 50 | throw er; // Unhandled 'error' event 51 | ^ 52 | Error: listen EADDRINUSE :::8080 53 | ..... 54 | ``` 55 | 可以在D:\\my-blog\\config\\index.js里修改端口 56 | ``` 57 | dev: { 58 | 59 | // Paths 60 | assetsSubDirectory: 'static', 61 | assetsPublicPath: '/', 62 | proxyTable: {}, 63 | 64 | // Various Dev Server settings 65 | host: 'localhost', // can be overwritten by process.env.HOST 66 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 67 | autoOpenBrowser: false, 68 | errorOverlay: true, 69 | notifyOnErrors: true, 70 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 71 | 72 | // https://webpack.js.org/configuration/devtool/#development 73 | devtool: 'cheap-module-eval-source-map', 74 | 75 | // If you have problems debugging vue-files in devtools, 76 | // set this to false - it *may* help 77 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 78 | cacheBusting: true, 79 | 80 | cssSourceMap: true 81 | }, 82 | ``` 83 | 启动成功后: 84 | ``` 85 | ... 86 | DONE Compiled successfully in 2597ms 87 | I Your application is running here: http://localhost:8080 88 | ``` 89 | ![启动成功](http://otr9a8wg0.bkt.clouddn.com/17-7-28/73659113.jpg) 90 | 91 | 92 | # 安装需要用到的包 93 | 首先安装项目要用到的一些组件,也可以之后遇到什么需要的再安装 94 | - element-ui:饿了么前段组件库,可以帮助快速建立起前段页面,少些很多样式 95 | - vuex:vue状态管理 96 | - axios:基于Promise 用于浏览器和 nodejs 的 HTTP 客户端 97 | - mysql:连接mysql数据库 98 | - express: 99 | - body-parser: 100 | - node-sass:sass-loader依赖 101 | - sass-loader:解析sass/scss文件 102 | 103 | 可以依次安装( npm 安装很慢的可以使用 cnpm ): 104 | ``` 105 | npm install element-ui --save (回车) 106 | npm install vuex --save (回车) 107 | npm install axios --save (回车) 108 | npm install mysql --save (回车) 109 | npm install express --save (回车) 110 | npm install body-parser --save (回车) 111 | npm install node-sass --save-dev (回车) 112 | npm install sass-loader --save-dev (回车) 113 | ``` 114 | 也可以一起安装: 115 | ``` 116 | npm install element-ui vuex axios mysql express body-parser --save (回车) 117 | npm install node-sass sass-loader --save-dev (回车) 118 | ``` 119 | --save 意思就是将依赖记录在 package.json 里的 dependencies 下,之后生产环境也是需要这些包的,--sava-dev 是将依赖记录在 package.json 里的 devDependencies 下,只是开发环境需要这些包,方便开发调试,而生产环境不需要。(-S 是 --save 的缩写,-D 是 --save-dev 的缩写) 120 | ``` 121 | "dependencies": { 122 | "axios": "^0.18.0", 123 | "body-parser": "^1.18.3", 124 | "element-ui": "^2.3.9", 125 | "express": "^4.16.3", 126 | "mysql": "^2.15.0", 127 | "vue": "^2.5.2", // 项目构建完就有了 128 | "vue-router": "^3.0.1", // 项目构建完就有了 当时"Install vue-router"选了Yes 129 | "vuex": "^3.0.1" 130 | }, 131 | "devDependencies": { 132 | ... 133 | "node-sass": "^4.9.0", 134 | "sass-loader": "^7.0.1", 135 | ... 136 | }, 137 | ``` 138 | 139 | ``` 140 | 使用scss/sass前必须先安装node-sass、sass-loader,否则运行npm run dev时会报错 141 | 147 | ``` 148 | 149 | # 调用后台接口 ajax 请求数据 150 | 151 | 1、打开入口js文件main.js,引入element-ui组件来搭建页面 [element-ui 查看官网文档](http://element.eleme.io/#/zh-CN/component/installation)。 152 | ``` 153 | // The Vue build version to load with the `import` command 154 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 155 | import Vue from 'vue' 156 | import App from './App' 157 | import router from './router' 158 | import axios from 'axios' 159 | import ElementUI from 'element-ui' 160 | import 'element-ui/lib/theme-chalk/index.css' 161 | 162 | Vue.config.productionTip = false 163 | Vue.use(ElementUI); 164 | Vue.prototype.$http = axios; 165 | 166 | /* eslint-disable no-new */ 167 | new Vue({ 168 | el: '#app', 169 | router, 170 | components: { App }, 171 | template: '' 172 | }) 173 | ``` 174 | 其中 axios 用来完成 ajax 请求, 175 | ``` 176 | import axios from 'axios' 177 | axios.get('/', function() {}); 178 | axios.post('/', function() {}); 179 | 180 | // 将 axios 添加的 Vue 原型上后就不需要再在每个需要使用它的页面引入了 181 | Vue.prototype.$http = axios; 182 | $http.get('/', function() {}); 183 | $http.post('/', function() {}); 184 | ``` 185 | 186 | 2、每个页面都相当于一个组件,文件以.vue结尾,第一次启动成功时看到的页面就是组件Hello.vue,路径src/components/Hello.vue。路由地址在 src/router/index.js 中配置,打开修改我们待会自己要用的: 187 | ``` 188 | import Vue from 'vue' 189 | import Router from 'vue-router' 190 | import HelloWorld from '@/components/HelloWorld' 191 | import Home from '@/components/pages/Home' 192 | import Blog from '@/components/pages/Blog' 193 | 194 | Vue.use(Router) 195 | 196 | export default new Router({ 197 | routes: [ 198 | // { 199 | // path: '/', 200 | // name: 'HelloWorld', 201 | // component: HelloWorld 202 | // }, 203 | { 204 | path: '/', // http://localhost:8080/#/ 205 | name: 'Home', 206 | component: Home 207 | }, 208 | { 209 | path: '/blog', // http://localhost:8080/#/blog 210 | name: 'Blog', 211 | component: Blog 212 | } 213 | ] 214 | }) 215 | ``` 216 | 上面引入了三个组件HelloWorld.vue(默认),Home.vue,Blog.vue。 217 | path是页面地址,name可以随便写,component 是 import 的组件名。 218 | 219 | 3、在 scr/components 下新建文件夹 pages,在 pages 下新建文件 Home.vue,Blog.vue,里面按规则要求写好内容,运行工程打开页面 http://localhost:8080/#/、http://localhost:8080/#/blog 即可看到相应的内容。 220 | 在 Blog.vue 中输入下面内容用来后面测试调用接口 221 | ``` 222 | 228 | 229 | 248 | ``` 249 | 4、后端使用Express做服务端提供数据接口,不了解的可以先去官网文档大致了解一下 [Express官网](http://expressjs.com/en/starter/installing.html),在根目录my-blog下创建文件夹server用来存放后端数据库配置数据和相关方法api。 250 | server文件夹下创建文件:index.js 251 | ``` 252 | const path = require('path'); 253 | const express = require('express'); 254 | const app = express(); 255 | 256 | app.get('/api/getArticle', (req, res, next) => { 257 | res.json({ 258 | data: '后台返回结果 getArticle' 259 | }) 260 | }) 261 | 262 | // 监听端口 263 | app.listen(3000); 264 | console.log('success listen at port:3000......'); 265 | ``` 266 | 另开一个 CMD 窗口,进入目录 D:\my-blog\server 267 | ``` 268 | D:\my-blog\server 269 | $ node index.js 270 | success listen at port:3000...... 271 | ``` 272 | 5、打开 http://localhost:8080/#/blog 点击按钮"调用后台接口",会发现控制台报错 273 | ![调后台接口报错_跨域](http://otr9a8wg0.bkt.clouddn.com/%E8%B0%83%E5%90%8E%E5%8F%B0%E6%8E%A5%E5%8F%A3%E6%8A%A5%E9%94%99_%E8%B7%A8%E5%9F%9F.jpg) 274 | 这是因为我们工程运行的端口是8080,而后端程序运行的端口是3000,所以是跨域请求,要想请求成功,就要先在配置里设置一下代理 275 | 276 | 6、打开文件 /config/index.js,将 proxyTable 项设置如下 277 | ``` 278 | proxyTable: { 279 | '/api': { 280 | target: 'http://localhost:3000/api', 281 | changeOrigin: true, 282 | pathRewrite: { 283 | '^/api': '' 284 | } 285 | } 286 | } 287 | 288 | ``` 289 | - '/api': 表示所有以 /api 为开头的请求,如我们的请求 this.$http.get('/api/getArticle') 290 | - target: 将所有以 /api 为开头请求转发到 http://localhost:3000/api 291 | - changeOrigin: true/false, Default: false,本地会虚拟一个服务端接收你的请求并代你发送该请求(不太明白,false 试了也可以) 292 | - pathRewrite: 重写地址。 '^/api': '' 表示将以 /api 开头的请求的地址中的 '/api' 替换为 '', 293 | 即 path = path.replace(/^\/api/, '') 294 | eg: this.\$http.get('/api/getArticle') 295 | path = '/api/getArticle' 296 | path = path.replace(/^\/api/, '') = '/getArticle' 297 | 这样目标请求就变成了 http://localhost:3000/api/getArticle , 298 | 如果不写 pathRewrite, 请求则为 http://localhost:3000/api/api/getArticle 所以也可以这样 299 | ``` 300 | proxyTable: { 301 | '/api': { 302 | target: 'http://localhost:3000', 303 | changeOrigin: true, 304 | } 305 | } 306 | ``` 307 | 最后请求同样转发为 http://localhost:3000/api/getArticle , 总之要和后台的接口路径对应上,不过还是建议加上 pathRewrite,方便同类方法调用 308 | ``` 309 | // server/index.js 310 | const path = require('path'); 311 | const express = require('express'); 312 | const router = express.Router(); 313 | const app = express(); 314 | 315 | app.use('/add', router); 316 | app.use('/del', router); 317 | 318 | router.get('/getArticle1', (req, res, next) => { 319 | api.getArticle(req, res, next); 320 | }) 321 | router.get('/getArticle2', (req, res, next) => { 322 | api.getArticle(req, res, next); 323 | }) 324 | 325 | router.get('/delArticle1', (req, res, next) => { 326 | api.getArticle(req, res, next); 327 | }) 328 | router.get('/delArticle2', (req, res, next) => { 329 | api.getArticle(req, res, next); 330 | }) 331 | 332 | // 监听端口 333 | app.listen(3000); 334 | console.log('success listen at port:3000......'); 335 | 336 | ---------------------------------------------- 337 | 338 | // congif/index.js 339 | proxyTable: { 340 | '/add': { 341 | target: 'http://localhost:3000/add', 342 | changeOrigin: true, 343 | pathRewrite: { 344 | '^/add': '' 345 | } 346 | }, 347 | '/del': { 348 | target: 'http://localhost:3000/del', 349 | changeOrigin: true, 350 | pathRewrite: { 351 | '^/del': '' 352 | } 353 | }, 354 | }, 355 | ``` 356 | 357 | 7、正确返回数据 358 | ![](http://otr9a8wg0.bkt.clouddn.com/%E5%90%8E%E5%8F%B0%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%9C.jpg) 359 | 360 | 361 | # 数据库存取数据(Mysql) 362 | Mysql可视化工具我用的是Navicat For Mysql,新建连接,数据库,数据表,查询等都可在其中完成,当然熟悉命令的都可以在cmd中命令完成 363 | ### Mysql 新建连接 364 | ![新建数据库](http://otr9a8wg0.bkt.clouddn.com/%E6%96%B0%E5%BB%BA%E6%95%B0%E6%8D%AE%E5%BA%93Mysql.jpg) 365 | ### 连接数据库 366 | 在 src/server 下新建文件 db.js,写入下面代码 367 | ```js 368 | const mysql = require('mysql'); 369 | 370 | const mysqlConfig = { 371 | host: 'localhost', // 新建数据库连接时的 主机名或ID地址 内容 372 | user: 'root', 373 | password: '8023', // root 密码 374 | database: 'myBlog', // 数据库名 375 | port: '3306' 376 | } 377 | const pool = mysql.createPool({ 378 | host: mysqlConfig.host, 379 | user: mysqlConfig.user, 380 | password: mysqlConfig.password, 381 | database: mysqlConfig.database, 382 | port: mysqlConfig.port, 383 | multipleStatements: true // 多语句查询 384 | }); 385 | 386 | var setValue = function() { 387 | pool.getConnection((err, connection) => { 388 | var sql = 'INSERT INTO test(id, name) VALUES (1, "blog")' 389 | connection.query(sql, (err, result) => { 390 | console.log(result); 391 | connection.release(); 392 | }) 393 | }) 394 | } 395 | 396 | setValue(); 397 | ``` 398 | 引入包 mysql,创建连接池 mysql.createPool,sql语法和在命令中使用的形同,拼成字符串即可,在 server 目录下运行 db.js 文件,刷新数据库 399 | ![添加数据](http://otr9a8wg0.bkt.clouddn.com/dbSetValue.jpg) 400 | 401 | 同理可增删查改数据 402 | ```js 403 | // 查询数据,? 的值填入 connection.jquery 的第二个参数(数组)中 404 | // WHERE id = ? AND name = ? ---> connetion.query(sql, [1, "blog"], () => ) 405 | var getValue = function() { 406 | pool.getConnection((err, connection) => { 407 | var sql = 'SELECT * FROM test WHERE id = ?' 408 | connection.query(sql, [1], (err, result) => { 409 | console.log(result); 410 | connection.release(); 411 | }) 412 | }) 413 | } 414 | getValue(); 415 | 416 | /* 417 | $ node db.js 418 | [ RowDataPacket { id: '1', name: 'blog' } ] 419 | */ 420 | 421 | // 更新数据 422 | var updValue = function() { 423 | pool.getConnection((err, connection) => { 424 | var sql = 'UPDATE test SET name = ? WHERE id = ?' 425 | connection.query(sql, [22, 1], (err, result) => { 426 | console.log(result); 427 | connection.release(); 428 | }) 429 | }) 430 | } 431 | updValue(); 432 | 433 | // 删除数据 434 | var delValue = function() { 435 | pool.getConnection((err, connection) => { 436 | var sql = 'DELETE FROM test WHERE id = ?' 437 | connection.query(sql, [1], (err, result) => { 438 | console.log(result); 439 | connection.release(); 440 | }) 441 | }) 442 | } 443 | delValue(); 444 | ``` 445 | 结合前面的 ajax 请求数据,我们便可以轻松的对数据库中的数据进行操作了,下面来模块化这些操作。 446 | ### 模块化后端代码 447 | 在 /server 下创建文件 448 | - db.js 数据库连接配置 449 | - api.js 连接数据库,各种方法实现 450 | - sqlMap.js sql语句 451 | - router.js 后端 express 路由配置 452 | - index.js 后端入口文件,启动后端服务 453 | 454 | 1、db.js 455 | ```js 456 | // 数据库连接配置 457 | module.exports = { 458 | mysql: { 459 | host: 'localhost', // 新建数据库连接时的 主机名或ID地址 内容 460 | user: 'root', 461 | password: '8023', // root 密码 462 | database: 'myBlog', // 数据库名 463 | port: '3306' 464 | } 465 | } 466 | ``` 467 | 2、api.js 468 | ```js 469 | const mysql = require('mysql'); 470 | const dbConfig = require('./db'); 471 | const sqlMap = require('./sqlMap'); 472 | 473 | const pool = mysql.createPool({ 474 | host: dbConfig.mysql.host, 475 | user: dbConfig.mysql.user, 476 | password: dbConfig.mysql.password, 477 | database: dbConfig.mysql.database, 478 | port: dbConfig.mysql.port, 479 | multipleStatements: true // 多语句查询 480 | }); 481 | 482 | module.exports = { 483 | getValue(req, res, next) { 484 | var id = req.query.id; 485 | pool.getConnection((err, connection) => { 486 | var sql = sqlMap.getValue; 487 | connection.query(sql, [id], (err, result) => { 488 | res.json(result); 489 | connection.release(); 490 | }) 491 | }) 492 | }, 493 | setValue(req, res, next) { 494 | console.log(req.body); 495 | var id = req.body.id, name = req.body.name; 496 | pool.getConnection((err, connection) => { 497 | var sql = sqlMap.setValue; 498 | connection.query(sql, [name, id], (err, result) => { 499 | res.json(result); 500 | connection.release(); 501 | }) 502 | }) 503 | } 504 | } 505 | 506 | ``` 507 | 3、sqlMap.js 508 | ```js 509 | var sqlMap = { 510 | getValue: 'SELECT * FROM test WHERE id = ?', 511 | setValue: 'UPDATE test SET name = ? WHERE id = ?' 512 | } 513 | 514 | module.exports = sqlMap; 515 | ``` 516 | 4、router.js 517 | ```js 518 | const express = require('express'); 519 | const router = express.Router(); 520 | const api = require('./api'); 521 | 522 | router.get('/getValue', (req, res, next) => { 523 | api.getValue(req, res, next); 524 | }); 525 | 526 | router.post('/setValue', (req, res, next) => { 527 | api.setValue(req, res, next); 528 | }); 529 | 530 | module.exports = router; 531 | ``` 532 | 5、index.js 533 | ```js 534 | const routerApi = require('./router'); 535 | const bodyParser = require('body-parser'); // post 数据是需要 536 | const express = require('express'); 537 | const app = express(); 538 | 539 | app.use(bodyParser.json()); 540 | 541 | // 后端api路由 542 | app.use('/api', routerApi); 543 | 544 | // 监听端口 545 | app.listen(3000); 546 | console.log('success listen at port:3000......'); 547 | ``` 548 | 549 | 在 /scr/components/pages/Blog.vue 文件中写入下面代码测试 550 | ```js 551 | 558 | 559 | 588 | ``` 589 | - get:第二个参数(可选)是一个对象,以 params 为属性,将条件数据传到后台,后台通过 req.query 可以获得 params 对应的值 590 | - post:第二个参数(可选)也是一个对象,属性任意,将提交数据传到后台,后台通过 req.body 可以获得这个对象,req.body 数据的解析需要用到包 body-parser,在 index.js 中引入 use 即可。 591 | 592 | 打开两个命令窗口分别运行工程,运行后端服务,即可进行测试: 593 | ```js 594 | D:\my-blog 595 | $ npm run dev 596 | ``` 597 | ```js 598 | D:\my-blog\server 599 | $ node index.js 600 | ``` --------------------------------------------------------------------------------