├── README.md ├── favicon.ico ├── index.html ├── package.json ├── php └── api │ └── index.php ├── screenshot ├── wifi0s0-1823416679.png ├── wifi0s0-9460455.png └── wifi0s0-947228272.png ├── src ├── components │ ├── head.vue │ ├── header.vue │ └── returnTop.vue ├── css │ └── reset.css ├── filters.js ├── img │ ├── demo.gif │ ├── loading.gif │ ├── nav.png │ ├── old.jpg │ └── returnTop.png ├── js │ └── equ.js ├── main.js ├── scss │ └── home.scss └── vue │ ├── article.vue │ ├── artlist.vue │ └── loading.vue └── webpack.config.js /README.md: -------------------------------------------------------------------------------- 1 | # v2ex_vue 2 | v2ex的vue小试项目 3 | 4 | ## 原版项目 5 | https://github.com/cwsjoker/Cnode-vue-spa 6 | 7 | ## 参考的API 8 | [https://www.v2ex.com/p/7v9TEc53](https://www.v2ex.com/p/7v9TEc53) 9 | 10 | [https://github.com/djyde/V2EX-API](https://github.com/djyde/V2EX-API) 11 | 12 | ## 配置 13 | 把项目下的php/api文件移到到你的PHP环境下 14 | 修改v2ex Vue项目 src/components/vue 目录下组件中的地址为你的PHP后端服地址。 15 | 16 | ## 运行安装 17 | 18 | - npm install 19 | - npm start 20 | 21 | ## 学习交流讨论群 22 | QQ群:571107753 23 | 24 | ## 图片 25 | ![v2ex的vue小试项目](https://github.com/dabpop139/v2ex_vue/blob/master/screenshot/wifi0s0-1823416679.png) 26 | ![v2ex的vue小试项目](https://github.com/dabpop139/v2ex_vue/blob/master/screenshot/wifi0s0-947228272.png) 27 | ![v2ex的vue小试项目](https://github.com/dabpop139/v2ex_vue/blob/master/screenshot/wifi0s0-9460455.png) 28 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | V2EX.COM VUE SPA 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "main.js", 3 | "scripts": { 4 | "start": "webpack-dev-server --hot --inline" 5 | }, 6 | "devDependencies": { 7 | "babel-core": "^6.9.1", 8 | "babel-loader": "^6.2.4", 9 | "babel-plugin-transform-runtime": "^6.9.0", 10 | "babel-preset-es2015": "^6.9.0", 11 | "babel-runtime": "^6.9.2", 12 | "css-loader": "^0.23.1", 13 | "extract-text-webpack-plugin": "^1.0.1", 14 | "file-loader": "^0.8.5", 15 | "html-webpack-plugin": "^2.16.1", 16 | "jquery": "^2.2.3", 17 | "moment": "^2.13.0", 18 | "node-sass": "^3.8.0", 19 | "sass-loader": "^3.2.0", 20 | "style-loader": "^0.13.1", 21 | "url-loader": "^0.5.7", 22 | "vue": "^1.0.24", 23 | "vue-hot-reload-api": "^1.3.2", 24 | "vue-html-loader": "^1.2.2", 25 | "vue-loader": "^8.5.2", 26 | "vue-resource": "^0.7.2", 27 | "vue-router": "^0.7.13", 28 | "vue-style-loader": "^1.0.0", 29 | "webpack": "^1.13.0", 30 | "webpack-dev-server": "^1.14.1", 31 | "webpack-zepto": "^0.0.1" 32 | } 33 | } -------------------------------------------------------------------------------- /php/api/index.php: -------------------------------------------------------------------------------- 1 | array('timeout' => $timeout))); 34 | return @file_get_contents($url, 0, $stream); 35 | } 36 | 37 | function http_request($url,$timeout=30,$header=array()){ 38 | if (!function_exists('curl_init')) { 39 | throw new Exception('server not install curl'); 40 | } 41 | $ch = curl_init(); 42 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 43 | curl_setopt($ch, CURLOPT_HEADER, true); 44 | curl_setopt($ch, CURLOPT_URL, $url); 45 | curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 46 | if (!empty($header)) { 47 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 48 | } 49 | $data = curl_exec($ch); 50 | list($header, $data) = explode("\r\n\r\n", $data); 51 | $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 52 | if ($http_code == 301 || $http_code == 302) { 53 | $matches = array(); 54 | preg_match('/Location:(.*?)\n/', $header, $matches); 55 | $url = trim(array_pop($matches)); 56 | curl_setopt($ch, CURLOPT_URL, $url); 57 | curl_setopt($ch, CURLOPT_HEADER, false); 58 | $data = curl_exec($ch); 59 | } 60 | 61 | if ($data == false) { 62 | curl_close($ch); 63 | } 64 | @curl_close($ch); 65 | return $data; 66 | } 67 | 68 | function http_curl_request($url,$timeout=30){ 69 | $ch = curl_init(); 70 | curl_setopt($ch, CURLOPT_URL,$url); 71 | curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 72 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //不直接输出 73 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 74 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 75 | $result = curl_exec($ch); 76 | return $result; 77 | } 78 | 79 | function raw_input_stream(){ 80 | $raw_post_data = file_get_contents('php://input', 'r'); 81 | return json_decode($raw_post_data, true); 82 | } 83 | 84 | function fiteval($json){ 85 | return '('.json_encode($json).')'; 86 | } 87 | 88 | function resmsg($msg, $status = true){ 89 | exit(json_encode(array( 90 | 'msg' => $msg, 91 | 'success' => $status 92 | ))); 93 | } 94 | 95 | if($tab=='topics_latest'){ 96 | echo '{"success":true,"data":'; 97 | echo http_curl_request('https://www.v2ex.com/api/topics/latest.json'); 98 | echo '}'; 99 | exit(); 100 | } 101 | 102 | if($tab=='topics_hot'){ 103 | echo '{"success":true,"data":'; 104 | echo http_curl_request('https://www.v2ex.com/api/topics/hot.json'); 105 | echo '}'; 106 | exit(); 107 | } 108 | 109 | if(strstr($tab,'topics_show_')){ 110 | $tab=str_replace('topics_show_', '', $tab); 111 | echo '{"success":true,"data":'; 112 | echo http_curl_request('https://www.v2ex.com/api/topics/show.json?node_name='.$tab); 113 | echo '}'; 114 | exit(); 115 | } 116 | 117 | if(strstr($act,'show_id_')){ 118 | $act=str_replace('show_id_', '', $act); 119 | echo '{"success":true,"data":'; 120 | echo http_curl_request('https://www.v2ex.com/api/topics/show.json?id='.$act); 121 | echo '}'; 122 | exit(); 123 | } 124 | 125 | if(strstr($act,'replies_show_')){ 126 | $act=str_replace('replies_show_', '', $act); 127 | echo '{"success":true,"data":'; 128 | echo http_curl_request('https://www.v2ex.com/api/replies/show.json?page_size=20&topic_id='.$act.'&page=1'); 129 | echo '}'; 130 | exit(); 131 | } 132 | ?> -------------------------------------------------------------------------------- /screenshot/wifi0s0-1823416679.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/screenshot/wifi0s0-1823416679.png -------------------------------------------------------------------------------- /screenshot/wifi0s0-9460455.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/screenshot/wifi0s0-9460455.png -------------------------------------------------------------------------------- /screenshot/wifi0s0-947228272.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/screenshot/wifi0s0-947228272.png -------------------------------------------------------------------------------- /src/components/head.vue: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/header.vue: -------------------------------------------------------------------------------- 1 | 7 | 30 | -------------------------------------------------------------------------------- /src/components/returnTop.vue: -------------------------------------------------------------------------------- 1 | 4 | 28 | -------------------------------------------------------------------------------- /src/css/reset.css: -------------------------------------------------------------------------------- 1 | @charset 'utf-8'; 2 | /* CSS reset */ 3 | html{color:#000;background:#FFF;font-family:'Microsoft Yahei','宋体',Arial;} 4 | html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,p,td,strong{padding:0;margin:0;font-family:'Microsoft Yahei','宋体',Arial;} 5 | table{border-collapse:collapse;border-spacing:0;} 6 | fieldset,img{border:0;} 7 | a{text-decoration:none; color:#00c; outline:none;}/*此处待添加默认链接颜色*/ 8 | var,em,strong{font-style:normal;} 9 | address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;} 10 | del,ins{text-decoration:none;} 11 | li{list-style:none;} 12 | caption,th{text-align:left;} 13 | h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} 14 | q:before,q:after{content:'';} 15 | abbr,acronym{border:0;font-variant:normal;} 16 | sup{vertical-align:baseline;} 17 | sub{vertical-align:baseline;} 18 | legend{color:#000;} 19 | input,button,textarea,select,optgroup,option{font-family:inherit; font-size:inherit;font-style:inherit;font-weight:inherit;} 20 | input,button,textarea,select{*font-size:100%;} 21 | input{border:none;outline:none;padding: 0px;} 22 | abbr, article, aside, audio, canvas, datalist, details, dialog, eventsource, figure, footer, header, hgroup, mark, menu, meter, nav, output, progress, section, time, video{display: block;} 23 | body{-webkit-user-select:none;-webkit-text-size-adjust:none;/*-webkit-transform-style:preserve-3d;*/} 24 | *{-webkit-tap-highlight-color:rgba(0,0,0,0);} 25 | .clearfix:after { 26 | content:"\200B"; 27 | display:block; 28 | height:0; 29 | clear:both; 30 | } 31 | .clearfix { 32 | *zoom:1; 33 | } -------------------------------------------------------------------------------- /src/filters.js: -------------------------------------------------------------------------------- 1 | exports.getDateTime = (dataTime) => { 2 | let time = dataTime; 3 | let str = time.substring(0, 10); 4 | return str; 5 | } 6 | 7 | 8 | // 获取本帖的标签 9 | exports.getArticleTab = (tab, good, top) => { 10 | let str = ''; 11 | if(top){ 12 | str = '置顶'; 13 | }else if(good){ 14 | str = '精华'; 15 | }else{ 16 | switch(tab){ 17 | case 'share': 18 | str = '分享'; 19 | break; 20 | case 'ask': 21 | str = '问答'; 22 | break; 23 | case 'job': 24 | str = '招聘'; 25 | break; 26 | default: 27 | str = "无"; 28 | break; 29 | } 30 | } 31 | return str; 32 | } 33 | 34 | // 判断是否为置顶或是精华的帖子 35 | exports.getArticleClass = (good, top) => { 36 | let className = ''; 37 | if(top) { 38 | className = 'put_top'; 39 | }else if(good) { 40 | className = 'put_good'; 41 | }else{ 42 | className = ''; 43 | } 44 | return className; 45 | } 46 | 47 | // 判断发帖时间与现在时间的间隔 48 | exports.getLastTime = (creatTime) => { 49 | // let oldtime = creatTime.substring(0, 10); 50 | // let newtime = new Date(); 51 | 52 | // let oldtime = new Date(creatTime); 53 | 54 | var timestamp = creatTime; 55 | var oldtime = new Date(); 56 | oldtime.setTime(timestamp * 1000); 57 | 58 | let newtime = (new Date() - oldtime)/1000; 59 | let month = Math.floor(newtime/3600/24/30); 60 | let day = Math.floor(newtime/3600/24); 61 | let hours = Math.floor(newtime/3600); 62 | let mins = Math.floor(newtime/60); 63 | let str = ''; 64 | if(hours === 0){ 65 | str = mins + '分钟前'; 66 | }else if(day === 0){ 67 | str = hours + '小时前'; 68 | }else if(month === 0){ 69 | str = day + '天前'; 70 | }else { 71 | str = month + '月前'; 72 | } 73 | return str; 74 | } -------------------------------------------------------------------------------- /src/img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/src/img/demo.gif -------------------------------------------------------------------------------- /src/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/src/img/loading.gif -------------------------------------------------------------------------------- /src/img/nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/src/img/nav.png -------------------------------------------------------------------------------- /src/img/old.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/src/img/old.jpg -------------------------------------------------------------------------------- /src/img/returnTop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabpop139/v2ex_vue/b73d04c5b7be4b79067c37e35f75929c9b94a365/src/img/returnTop.png -------------------------------------------------------------------------------- /src/js/equ.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $(window).resize(infinite); 3 | function infinite() { 4 | var htmlWidth = $('html').width(); 5 | if (htmlWidth <= 320) { 6 | $("html").css({ 7 | "font-size" : "12px" 8 | }); 9 | }else if(htmlWidth >= 720) { 10 | $("html").css({ 11 | "font-size" : "28.8px" 12 | }); 13 | } else { 14 | $("html").css({ 15 | "font-size" : 28.8 / 720 * htmlWidth + "px" 16 | }); 17 | } 18 | }infinite(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | var Vue = require('vue'); 2 | var VueRouter = require('vue-router'); 3 | var VueResource = require('vue-resource'); 4 | 5 | // 过滤器 6 | var filters = require('./filters'); 7 | //引入css重置文件,基本的样式文件 8 | require('./css/reset.css'); 9 | require('./scss/home.scss'); 10 | // 引入px与rem的换算 11 | require('./js/equ.js'); 12 | 13 | //实例化vue模块 14 | Vue.use(VueRouter); 15 | Vue.use(VueResource); 16 | 17 | // 实例化过滤器 18 | // Vue.filter('getDateTime', filters.getDateTime); 19 | Object.keys(filters).forEach(k => Vue.filter(k, filters[k])); 20 | 21 | // 创建一个空组件 22 | var app = Vue.extend({}); 23 | 24 | //实例化VueRouter 25 | var router = new VueRouter({ 26 | // 当hashbang的值为true时,所有的路径都会被格式化已#!开头, 27 | hashbang: true, 28 | history: false, 29 | saveScrollPosition: true, 30 | transitionOnLoad: true //加载过渡 31 | }); 32 | 33 | // 路由表 34 | router.map({ 35 | '/':{ 36 | //首页 37 | component: function (resolve) { 38 | require(['./vue/loading.vue'],resolve) 39 | } 40 | }, 41 | '/home':{ 42 | //首页 43 | name : 'home', 44 | component: function (resolve) { 45 | require(['./vue/loading.vue'],resolve) 46 | } 47 | }, 48 | '/artlist':{ 49 | //列表 50 | name : 'artlist', 51 | component: function (resolve) { 52 | require(['./vue/artlist.vue'],resolve) 53 | } 54 | }, 55 | '/artlist/article/:id':{ 56 | //文章详情 57 | name : 'article', 58 | component: function (resolve) { 59 | require(['./vue/article.vue'],resolve) 60 | } 61 | }, 62 | }); 63 | 64 | //默认/重定向到home页 65 | // router.redirect({ 66 | // '/':"/home" 67 | // }) 68 | router.afterEach(function (transition) { 69 | console.log('成功浏览到: ' + transition.to.path) 70 | }); 71 | 72 | router.start(app, "#app"); -------------------------------------------------------------------------------- /src/scss/home.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | min-height:100%; 3 | } 4 | body { 5 | margin: 0 auto; 6 | max-width:720px; 7 | background: #E1E1E1; 8 | } -------------------------------------------------------------------------------- /src/vue/article.vue: -------------------------------------------------------------------------------- 1 | 33 | 82 | -------------------------------------------------------------------------------- /src/vue/artlist.vue: -------------------------------------------------------------------------------- 1 | 27 | 98 | -------------------------------------------------------------------------------- /src/vue/loading.vue: -------------------------------------------------------------------------------- 1 | 12 | 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var WebpackDevServer = require('webpack-dev-server'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var vue = require("vue-loader"); 6 | 7 | //定义了一些文件夹的路径 8 | var ROOT_PATH = path.resolve(__dirname); 9 | var APP_PATH = path.resolve(ROOT_PATH, 'src/main.js'); 10 | var BUILD_PATH = path.resolve(ROOT_PATH, 'build'); 11 | 12 | var plugins = [ 13 | //压缩js 14 | // new webpack.optimize.UglifyJsPlugin({minimize: true}), 15 | //提公用js到common.js文件中 16 | new webpack.optimize.CommonsChunkPlugin('common.js'), 17 | //将样式统一发布到style.css中 18 | new ExtractTextPlugin("style.css", { 19 | allChunks: true 20 | }), 21 | // 使用 ProvidePlugin 加载使用率高的依赖库 22 | new webpack.ProvidePlugin({ 23 | $: 'webpack-zepto' 24 | }) 25 | ]; 26 | 27 | module.exports = { 28 | //项目的文件夹 可以直接用文件夹名称 默认会找index.js 也可以确定是哪个文件名字 29 | entry: { 30 | build : APP_PATH 31 | }, 32 | //输出的文件名 合并以后的js会命名为bundle.js 33 | output: { 34 | path: BUILD_PATH, 35 | filename: '[name].js', 36 | // 指向异步加载的路径 37 | publicPath : '/build/', 38 | // 非主文件的命名规则 39 | chunkFilename: '[id].build.js?[chunkhash]' 40 | }, 41 | module: { 42 | loaders: [ 43 | { 44 | test: /\.vue$/, 45 | loader: 'vue', 46 | }, 47 | { 48 | test: /\.scss$/, 49 | loader: ExtractTextPlugin.extract("style-loader", 'css-loader') 50 | }, 51 | { 52 | test: /\.css$/, 53 | loader: ExtractTextPlugin.extract("style-loader", "css-loader") 54 | }, 55 | { 56 | test: /\.(png|jpg|gif)$/, 57 | loader: 'url?limit=40000' 58 | }, 59 | /*{ 60 | test: /\.jsx?$/, 61 | loader: 'babel', 62 | include: APP_PATH, 63 | query: { 64 | presets: ['es2015'] 65 | } 66 | }*/ 67 | { test: /\.js$/, loader: 'babel', exclude: /node_modules/}, 68 | ] 69 | }, 70 | vue: { 71 | css: ExtractTextPlugin.extract("css"), 72 | sass: ExtractTextPlugin.extract("css!sass-loader") 73 | }, 74 | resolve: { 75 | extensions: ['', '.js', '.vue'], 76 | alias: { 77 | components: path.join(__dirname, './src/components') 78 | } 79 | }, 80 | devtool: '#source-map', 81 | babel: { 82 | presets: ['es2015'], 83 | plugins: ['transform-runtime'] 84 | }, 85 | devServer: { 86 | host: '192.168.1.110', 87 | historyApiFallback: true, 88 | hot: true, 89 | inline: true, 90 | progress: true, //打包进度反馈 91 | }, 92 | plugins: plugins 93 | }; 94 | --------------------------------------------------------------------------------