1. 初始化项目
① vue init webpack blog //然后一直回车,或者不用e2e, test, unit
② 修改package.json
1 "scripts": { 2 "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 3 "start": "node app.js", 4 "unit": "jest --config test/unit/jest.conf.js --coverage", 5 "e2e": "node test/e2e/runner.js", 6 "test": "npm run unit && npm run e2e", 7 "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", 8 "build": "node build/build.js" 9 }
安装express框架,在根目录下创建app.js,和用express4.0创建的模板差不多,关键在于以下代码
app.use("/*", express.static(path.join(__dirname, "dist")));
这样开启服务后就可以访问打包后的dist文件夹下面的index.html
③ 添加global文件夹全局配置
修改config/index 文件下的开发端口,改成可全局配置
port: config.devHotPort, //默认为监听8080
2. 获取36氪资讯
由于是服务端渲染的,采用以往的http+cheerio模块是获取不到动态生成的网页的,所以需要安装cnpm install phantom --save;然后再用cheerio格式化;
具体的教程可以看npm官网的首个例子,或者我GitHub上的代码;
1 getSight(req, res) { 2 let url = "http://36kr.com/", content, instance, page, status; 3 let message = []; 4 5 //文件格式{sightList : [], expire: ''} 6 return file.readFile('files/mySelf/sightCache.json').then( result => { 7 //缓存两个小时 8 if(result.expire && (Date.now() - result.expire) < 7200000){ 9 message = result.sightList; 10 return{ 11 data: message, 12 str: "获取每日资讯列表" 13 } 14 }else{ 15 return phantom.create().then( result => { 16 instance = result; 17 return result.createPage() 18 }).then( result => { 19 page = result; 20 return page.open(url) 21 }).then( result => { 22 if(result = "success"){ 23 return page.property('content'); 24 }else{ 25 this.paramError('url'); 26 } 27 }).then( result => { 28 content = result; 29 return instance.exit(); 30 }).then( result => { 31 let $ = cheerio.load(content, {decodeEntities: false}); 32 $("li.real_time_wrapper").each(function(){ 33 //console.log($(this).find('span.title').text()) 34 message.push({ 35 title : $(this).find('span.title').text(), 36 brief : $(this).find('div.show-content').text() 37 }) 38 }); 39 let tempStr = { 40 sightList : message, 41 expire : Date.now() 42 }; 43 return file.writeFile('files/mySelf/sightCache.json', JSON.stringify(tempStr)) 44 45 }).then( result => { 46 return{ 47 data: message, 48 str: "获取每日资讯列表" 49 } 50 }) 51 } 52 }) 53 }
1 static readFile(path){ 2 return new Promise( (resolve, reject) => { 3 if(!fs.existsSync(path)){ 4 //fs.writeFileSync(path,'')没必要,且创建后,很奇怪发现上面17行return不了 5 return resolve({}) 6 } 7 let readstream = fs.createReadStream(path); 8 let bufArr = []; 9 let bufLen = 0, buf; 10 return readstream.on('data', chunk => { 11 bufArr.push(chunk); 12 bufLen += chunk.length; 13 }).on('end', () => { 14 try{ 15 buf = Buffer.alloc(bufLen); 16 for(let i = 0, pos = 0; i<bufArr.length&&pos<bufLen; i++){ 17 bufArr[i].copy(buf,pos); 18 pos += bufArr[i].length; 19 } 20 let result = buf.toString(); 21 if(result.length > 0 && result.indexOf('{') > -1){ 22 return resolve(JSON.parse(result)) 23 }else{ 24 return resolve(result) 25 } 26 }catch( e ){ 27 log.error( e.message, "readFile" ); 28 reject( e ); 29 } 30 }) 31 }).catch( e => { 32 log.error( e.message, "readFile" ); 33 throw new Error( code.fileSysError.code ); 34 }); 35 }
3. axios进行http请求以及通过webpack设置代理(因为项目运行时前后端各占用一个端口)
webpack也是通过http-proxy-middleware包实现http代理
//跨域无法请求的时候我们可以修改工程下config文件夹下的index.js中的dev:{}部分。 dev: { env: require('./dev.env'), port: config.devHotPort, autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: { [config.proxyPrefix]: { target: 'http://localhost:' + config.port, changeOrigin: true, pathRewrite: { ['^'+config.proxyPrefix]: '' } } }, cssSourceMap: false } }
1 //在开发环境中进行配置,不能为空字符串,不然前端不会热更新,只会请求服务端代码,配置代理从而前后端都能热加载,生产环境记得改为“”; 2 axios.interceptors.request.use(function(config) { 3 if(globalConfig.proxyPrefix){ 4 config.url = globalConfig.proxyPrefix + config.url; 5 } 6 return config; 7 }, 8 function(error) { 9 // Do something with request error 10 return Promise.reject(error); 11 } 12 );
安装其他插件的时候,可以直接在 main.js 中引入并 Vue.use(),但是 axios 并不能 use,只能每个需要发送请求的组件中即时引入;
为了解决这个问题,有两种开发思路,一是在引入 axios 之后,修改原型链;二是结合 Vuex,封装一个 aciton。
import axios from 'axios' Vue.prototype.$ajax = axios //然后可以在组件中调用 this.$ajax({ method : 'post', url : '/Interface/getSight', data : {} }).then( result => { switch(result.data.retcode){ case 0: for(let i = 0; i < result.data.retdata.length; i++){ self.sightStatus.push(false); } self.sightList = result.data.retdata; break; } }).catch( err => { console.log(err) })
4. node作为代理服务器(中间层)
vue-cli 的npm start 执行的脚本是 "npm run dev",所以和开发环境一样,上生产中,我们可以用niginx做代理服务器进行分发,在这个项目中,直接用node做代理服务器
1 /** 2 * 作为代理服务器接口,暂未对data进行buffer二进制处理 3 * @version 2017-05-29 4 * @param {string} url 请求链接 5 * @param {object} data 请求参数 6 * @param {string} method 请求类型 7 */ 8 proxyRequest( req, res, url, data, method = "post"){ 9 10 let options, httpClient, tempOptions; 11 tempOptions = urlParser.parse(config.serverUrl + url) 12 options = { 13 hostname : tempOptions.hostname, 14 port : tempOptions.port, 15 path : tempOptions.pathname, 16 method : method, 17 encoding : "utf-8" 18 } 19 options.headers = req.headers; 20 //默认传递json数据 21 data = JSON.stringify( data ); 22 23 switch( req.protocol ){ 24 case "http:": 25 httpClient = httpNative; 26 break; 27 28 case "https:": 29 httpClient = httpsNative; 30 break; 31 32 default: 33 httpClient = httpNative; 34 } 35 36 return new Promise( ( resolve, reject ) => { 37 let request = httpClient.request( options, function( response ){ 38 if( response.statusCode !== 200 ){ 39 reject( new Error( "httpReuest error, status : " + response.statusCode ) ); 40 } 41 let result, buf, bufArr, bufLen; 42 result = { 43 httpVersion : response.httpVersion, 44 httpStatusCode : response.statusCode, 45 headers : response.headers, 46 body : "", 47 trailers : response.trailers 48 }; 49 bufArr = []; 50 bufLen = 0; 51 return response.on( "data", function( chunk ){ 52 bufArr.push( chunk ); 53 bufLen += chunk.length; 54 }).on( "end", function(){ 55 try{ 56 buf = Buffer.alloc( bufLen ); 57 for( let i = 0, pos = 0; i < bufArr.length && pos < bufLen; i++ ){ 58 bufArr[ i ].copy( buf, pos ); 59 pos += bufArr[ i ].length; 60 } 61 //必须返回全部信息才能让客户端和服务器创建连接 62 //resolve( JSON.parse( buf.toString() ) ); 63 result.body = buf.toString(); 64 resolve( result ); 65 }catch( e ){ 66 reject( e ); 67 } 68 }); 69 }); 70 71 request.on( "error", error => { 72 log.error( "request : " + error.message, "Http" ); 73 reject( error ); 74 }); 75 76 if( data !== null ){ 77 request.write( data ); 78 } 79 80 request.end(); 81 }).catch( e => { 82 log.error( e.message, "Http" ); 83 throw new Error( code.httpError.code ); 84 }).then( result => { 85 let body = JSON.parse(result.body); 86 87 //必须把把响应头返回给客户端 88 for(let item in result.headers){ 89 res.set(item, result.headers[item]) 90 } 91 switch( body.retcode ){ 92 case 0: 93 result.body = body.retdata; 94 break; 95 96 case -1: 97 this.paramError( body.retmsg ); 98 break; 99 100 default: 101 throw new Error( body.retcode ); 102 } 103 return result; 104 }) 105 }
1 return this.proxyRequest(req, res, "/user/editUserInfo", {}).then( result => { 2 return { 3 data : result.body, 4 str : "修改用户信息" 5 } 6 })
在本教程中,采用JWT登录验证,但不打算把后端项目放到GitHub上,所以不使用this.proxyRequest进行请求,而是this.request, 然后把JWT放在中间层进行处理
5. vuex状态管理
6. 富文本编辑器的选择(vue-quill-editor)
1 npm install vue-quill-editor 2 npm install quill
1 import 'quill/dist/quill.core.css' 2 import 'quill/dist/quill.snow.css' 3 import VueQuillEditor from 'vue-quill-editor' 4 5 Vue.use(VueQuillEditor)//main.js中全局引入
将其封装成一个组件
https://surmon-china.github.io/vue-quill-editor/ 官方demo给了几个例子给你快速上手
quill-image-resize-module //用于图片resize,直接按照3.0版本在我自己实际开发过程中报modules undefined //后面我直接引入1.0版本的,则没有问题,需要可在github复制下来
对于图片单独放到云服务器问题的,我是采用在后端处理解决的(参考:https://www.jianshu.com/p/36b144b4cef8)