├── static └── .gitkeep ├── .eslintignore ├── src ├── assets │ ├── logo.png │ └── styles │ │ ├── iconfont │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.svg │ │ ├── mixins.scss │ │ ├── varibles.scss │ │ ├── reset.css │ │ ├── iconfont.css │ │ └── border.css ├── common │ ├── image │ │ └── loading.gif │ ├── fade │ │ └── Fade.vue │ └── gallary │ │ └── Gallary.vue ├── store │ ├── mutations.js │ ├── state.js │ └── index.js ├── App.vue ├── router │ └── index.js ├── pages │ ├── city │ │ ├── components │ │ │ ├── Header.vue │ │ │ ├── Alphabet.vue │ │ │ ├── Search.vue │ │ │ └── List.vue │ │ └── City.vue │ ├── detail │ │ ├── compoments │ │ │ ├── List.vue │ │ │ ├── Header.vue │ │ │ └── Banner.vue │ │ └── Detail.vue │ └── home │ │ ├── components │ │ ├── Swiper.vue │ │ ├── Weekend.vue │ │ ├── HomeRecommend.vue │ │ ├── Icons.vue │ │ └── HomeHeader.vue │ │ └── Home.vue └── main.js ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── .editorconfig ├── .gitignore ├── .babelrc ├── .postcssrc.js ├── index.html ├── .eslintrc.js ├── package.json └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccnobody/Qunar/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/common/image/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccnobody/Qunar/HEAD/src/common/image/loading.gif -------------------------------------------------------------------------------- /src/assets/styles/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccnobody/Qunar/HEAD/src/assets/styles/iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/assets/styles/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccnobody/Qunar/HEAD/src/assets/styles/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/styles/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccnobody/Qunar/HEAD/src/assets/styles/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/assets/styles/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin ellipsis(){ 2 | overflow: hidden; 3 | white-space: nowrap; 4 | text-overflow: ellipsis; 5 | } -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default{ 2 | changeCity(state,city){ 3 | state.city = city 4 | try{ 5 | localStorage.city = city 6 | }catch (e) {} 7 | } 8 | } -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/assets/styles/varibles.scss: -------------------------------------------------------------------------------- 1 | $bgColor: #00bcd4; 2 | $headerHeight: .86rem; 3 | $darkTextColor: #333; 4 | // *{touch-action: none;} 5 | .decollate{ 6 | height: .4rem; 7 | background: #eee; 8 | } -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | let defaultCity = '北京' 2 | try{ 3 | if(localStorage.city){ 4 | defaultCity = localStorage.city 5 | } 6 | }catch (e) {} 7 | 8 | export default{ 9 | city: defaultCity 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | static/mock 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import state from './state' 4 | import mutations from './mutations' 5 | Vue.use(Vuex) 6 | 7 | 8 | 9 | export default new Vuex.Store({ 10 | state: state, 11 | mutations: mutations 12 | }) 13 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | travel 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/common/fade/Fade.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | 27 | 41 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | // import Home from '@/pages/home/Home' 4 | // import City from '../pages/city/City' 5 | const Home = () => import('@/pages/home/Home') 6 | const City = () => import('../pages/city/City') 7 | const Detail = () => import('@/pages/detail/Detail') 8 | 9 | Vue.use(Router) 10 | 11 | export default new Router({ 12 | routes: [ 13 | { 14 | path: '/', 15 | name: 'Home', 16 | component: Home 17 | },{ 18 | path: '/city', 19 | name: 'City', 20 | component: City 21 | },{ 22 | path: '/detail/:id', 23 | name: Detail, 24 | component: Detail 25 | } 26 | ],scrollBehavior (to, from, savedPosition) { 27 | return { x: 0, y: 0 } 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /src/pages/city/components/Header.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 39 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import store from "./store" 7 | import 'babel-polyfill' 8 | import VueAwesomeSwiper from 'vue-awesome-swiper' 9 | import 'styles/reset.css' 10 | import 'styles/border.css' 11 | import 'styles/iconfont.css' 12 | import fastclick from 'fastclick' 13 | import 'swiper/dist/css/swiper.css' 14 | import VueLazyload from 'vue-lazyload' 15 | 16 | // or with options 17 | // Vue.use(VueLazyload, { 18 | // preLoad: 1.3, 19 | // loading: 'common/image/loading.gif', 20 | // attempt: 1 21 | // }) 22 | Vue.use(VueLazyload, { 23 | loading: require('common/image/loading.gif') 24 | }) 25 | 26 | Vue.config.devtools = true 27 | Vue.config.productionTip = false 28 | fastclick.attach(document.body) 29 | Vue.use(VueAwesomeSwiper) 30 | /* eslint-disable no-new */ 31 | new Vue({ 32 | el: '#app', 33 | router, 34 | store, 35 | components: { App }, 36 | template: '' 37 | }) 38 | -------------------------------------------------------------------------------- /src/pages/detail/compoments/List.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | 24 | 45 | -------------------------------------------------------------------------------- /src/pages/home/components/Swiper.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | 37 | 38 | 53 | -------------------------------------------------------------------------------- /src/pages/city/City.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /src/common/gallary/Gallary.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 42 | 43 | 69 | -------------------------------------------------------------------------------- /src/pages/detail/Detail.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 59 | 60 | 65 | -------------------------------------------------------------------------------- /src/assets/styles/reset.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8";html{background-color:#fff;color:#000;font-size:12px} 2 | body,ul,ol,dl,dd,h1,h2,h3,h4,h5,h6,figure,form,fieldset,legend,input,textarea,button,p,blockquote,th,td,pre,xmp{margin:0;padding:0} 3 | body,input,textarea,button,select,pre,xmp,tt,code,kbd,samp{line-height:1.5;font-family:tahoma,arial,"Hiragino Sans GB",simsun,sans-serif} 4 | h1,h2,h3,h4,h5,h6,small,big,input,textarea,button,select{font-size:100%} 5 | h1,h2,h3,h4,h5,h6{font-family:tahoma,arial,"Hiragino Sans GB","微软雅黑",simsun,sans-serif} 6 | h1,h2,h3,h4,h5,h6,b,strong{font-weight:normal} 7 | address,cite,dfn,em,i,optgroup,var{font-style:normal} 8 | table{border-collapse:collapse;border-spacing:0;text-align:left} 9 | caption,th{text-align:inherit} 10 | ul,ol,menu{list-style:none} 11 | fieldset,img{border:0} 12 | img,object,input,textarea,button,select{vertical-align:middle} 13 | article,aside,footer,header,section,nav,figure,figcaption,hgroup,details,menu{display:block} 14 | audio,canvas,video{display:inline-block;*display:inline;*zoom:1} 15 | blockquote:before,blockquote:after,q:before,q:after{content:"\0020"} 16 | textarea{overflow:auto;resize:vertical} 17 | input,textarea,button,select,a{outline:0 none;border: none;} 18 | button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0} 19 | mark{background-color:transparent} 20 | a,ins,s,u,del{text-decoration:none} 21 | sup,sub{vertical-align:baseline} 22 | html {overflow-x: hidden;height: 100%;font-size: 50px;-webkit-tap-highlight-color: transparent;} 23 | body {font-family: Arial, "Microsoft Yahei", "Helvetica Neue", Helvetica, sans-serif;color: #333;font-size: .28em;line-height: 1;-webkit-text-size-adjust: none;} 24 | hr {height: .02rem;margin: .1rem 0;border: medium none;border-top: .02rem solid #cacaca;} 25 | a {color: #FFF;text-decoration: none;} 26 | .gallary-img{width: 100%} -------------------------------------------------------------------------------- /src/pages/home/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/pages/home/components/Weekend.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 32 | 33 | 72 | -------------------------------------------------------------------------------- /src/pages/home/components/HomeRecommend.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 35 | 36 | 77 | -------------------------------------------------------------------------------- /src/pages/home/components/Icons.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 45 | 46 | 93 | -------------------------------------------------------------------------------- /src/pages/city/components/Alphabet.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 72 | 90 | -------------------------------------------------------------------------------- /src/pages/home/components/HomeHeader.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | 29 | 86 | 87 | -------------------------------------------------------------------------------- /src/pages/detail/compoments/Header.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 53 | 54 | 95 | -------------------------------------------------------------------------------- /src/pages/detail/compoments/Banner.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 54 | 55 | 93 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/api': { 15 | target: 'http://localhost:8080/static/mock', 16 | } 17 | }, 18 | 19 | // Various Dev Server settings 20 | host: 'localhost', // can be overwritten by process.env.HOST 21 | port: 8888, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 22 | autoOpenBrowser: false, 23 | errorOverlay: true, 24 | notifyOnErrors: true, 25 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 26 | 27 | // Use Eslint Loader? 28 | // If true, your code will be linted during bundling and 29 | // linting errors and warnings will be shown in the console. 30 | useEslint: true, 31 | // If true, eslint errors and warnings will also be shown in the error overlay 32 | // in the browser. 33 | showEslintErrorsInOverlay: false, 34 | 35 | /** 36 | * Source Maps 37 | */ 38 | 39 | // https://webpack.js.org/configuration/devtool/#development 40 | devtool: 'cheap-module-eval-source-map', 41 | 42 | // If you have problems debugging vue-files in devtools, 43 | // set this to false - it *may* help 44 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 45 | cacheBusting: true, 46 | 47 | cssSourceMap: true 48 | }, 49 | 50 | build: { 51 | // Template for index.html 52 | index: path.resolve(__dirname, '../dist/index.html'), 53 | 54 | // Paths 55 | assetsRoot: path.resolve(__dirname, '../dist'), 56 | assetsSubDirectory: 'static', 57 | assetsPublicPath: './', 58 | 59 | /** 60 | * Source Maps 61 | */ 62 | 63 | productionSourceMap: false, 64 | // https://webpack.js.org/configuration/devtool/#production 65 | devtool: '#source-map', 66 | 67 | // Gzip off by default as many popular static hosts such as 68 | // Surge or Netlify already gzip all static assets for you. 69 | // Before setting to `true`, make sure to: 70 | // npm install --save-dev compression-webpack-plugin 71 | productionGzip: false, 72 | productionGzipExtensions: ['js', 'css'], 73 | 74 | // Run the build command with an extra argument to 75 | // View the bundle analyzer report after build finishes: 76 | // `npm run build --report` 77 | // Set to `true` or `false` to always turn it on or off 78 | bundleAnalyzerReport: process.env.npm_config_report 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "travel", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "leslie_choi ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.19.0", 15 | "babel-polyfill": "^6.26.0", 16 | "better-scroll": "^1.15.2", 17 | "fastclick": "^1.0.6", 18 | "stylus": "^0.54.5", 19 | "stylus-loader": "^3.0.2", 20 | "vue": "^2.5.2", 21 | "vue-awesome-swiper": "^2.6.7", 22 | "vue-lazyload": "^1.3.1", 23 | "vue-router": "^3.0.1", 24 | "vuex": "^3.1.1" 25 | }, 26 | "devDependencies": { 27 | "autoprefixer": "^7.1.2", 28 | "babel-core": "^6.22.1", 29 | "babel-eslint": "^8.2.1", 30 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 31 | "babel-loader": "^7.1.1", 32 | "babel-plugin-syntax-jsx": "^6.18.0", 33 | "babel-plugin-transform-runtime": "^6.22.0", 34 | "babel-plugin-transform-vue-jsx": "^3.5.0", 35 | "babel-preset-env": "^1.3.2", 36 | "babel-preset-stage-2": "^6.22.0", 37 | "chalk": "^2.0.1", 38 | "copy-webpack-plugin": "^4.0.1", 39 | "css-loader": "^0.28.0", 40 | "eslint": "^4.15.0", 41 | "eslint-config-standard": "^10.2.1", 42 | "eslint-friendly-formatter": "^3.0.0", 43 | "eslint-loader": "^1.7.1", 44 | "eslint-plugin-import": "^2.7.0", 45 | "eslint-plugin-node": "^5.2.0", 46 | "eslint-plugin-promise": "^3.4.0", 47 | "eslint-plugin-standard": "^3.0.1", 48 | "eslint-plugin-vue": "^4.0.0", 49 | "extract-text-webpack-plugin": "^3.0.0", 50 | "file-loader": "^1.1.4", 51 | "friendly-errors-webpack-plugin": "^1.6.1", 52 | "html-webpack-plugin": "^2.30.1", 53 | "node-notifier": "^5.1.2", 54 | "node-sass": "^4.12.0", 55 | "optimize-css-assets-webpack-plugin": "^3.2.0", 56 | "ora": "^1.2.0", 57 | "portfinder": "^1.0.13", 58 | "postcss-import": "^11.0.0", 59 | "postcss-loader": "^2.0.8", 60 | "postcss-url": "^7.2.1", 61 | "rimraf": "^2.6.0", 62 | "sass-loader": "^7.1.0", 63 | "semver": "^5.3.0", 64 | "shelljs": "^0.7.6", 65 | "uglifyjs-webpack-plugin": "^1.1.1", 66 | "url-loader": "^0.5.8", 67 | "vue-loader": "^13.3.0", 68 | "vue-style-loader": "^3.0.1", 69 | "vue-template-compiler": "^2.5.2", 70 | "webpack": "^3.6.0", 71 | "webpack-bundle-analyzer": "^2.9.0", 72 | "webpack-dev-server": "^2.9.1", 73 | "webpack-merge": "^4.1.0" 74 | }, 75 | "engines": { 76 | "node": ">= 6.0.0", 77 | "npm": ">= 3.0.0" 78 | }, 79 | "browserslist": [ 80 | "> 1%", 81 | "last 2 versions", 82 | "not ie <= 8" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /src/assets/styles/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('./iconfont/iconfont.eot?t=1518407379870'); /* IE9*/ 3 | src: url('./iconfont/iconfont.eot?t=1518407379870#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'), 5 | url('./iconfont/iconfont.ttf?t=1518407379870') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 6 | url('./iconfont/iconfont.svg?t=1518407379870#iconfont') format('svg'); /* iOS 4.1- */ 7 | } 8 | 9 | .iconfont { 10 | font-family:"iconfont" !important; 11 | font-size:16px; 12 | font-style:normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | -------------------------------------------------------------------------------- /src/pages/city/components/Search.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 78 | 79 | 113 | 114 | -------------------------------------------------------------------------------- /src/pages/city/components/List.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 41 | 75 | 76 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/assets/styles/iconfont/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue全家桶模拟去哪儿网 WebApp 2 | 3 | 基于 Vue+ vuex + vue-router + vue-axios +better-scroll + Scss + ES6 等开发的 WebApp,UI 界面参考了去哪儿网。 4 | 5 | ## 安装 6 | 7 | ``` 8 | git clone git@github.com:leslie-choi/Qunar.git 9 | 10 | npm install 11 | ``` 12 | 13 | ## 运行 14 | 15 | ``` 16 | npm run dev 17 | 18 | ``` 19 | 20 | # 预览 21 | 22 | 由于没有部署到服务器,如果想要在自己手机运行的话,可以将电脑和手机处于同一局域网环境,在package.json文件中的script对象的dev属性添加 –host 0.0.0.0,Windows用户在cmd命令输入ipconfig,找出本地IP地址,然后替代前缀的localhost,重启项目之后,手机即可输入地址即可访问了。(下面的图片是在我手机上运行的) 23 | 24 | ![image](https://qunar-leslie.oss-cn-beijing.aliyuncs.com/phone.jpg) 25 | 26 | ## 首页 27 | 28 | ![image](https://qunar-leslie.oss-cn-beijing.aliyuncs.com/first_banner.png) 29 | ![image](https://qunar-leslie.oss-cn-beijing.aliyuncs.com/first_list.png) 30 | 31 | ## 城市切换页面 32 | 33 | ![image](https://qunar-leslie.oss-cn-beijing.aliyuncs.com/city.png) 34 | 35 | ## 详情页面 36 | 37 | ![image](https://qunar-leslie.oss-cn-beijing.aliyuncs.com/detail.png) 38 | 39 | ## 详情轮播页面 40 | 41 | ![image](https://qunar-leslie.oss-cn-beijing.aliyuncs.com/detail_banner.png) 42 | 43 | # 开发目的 44 | 45 | 通过学习开发一个 Vue 全家桶项目,让自己更熟练的使用 Vue 全家桶、模块化开发、ES6 等等知识,提高自己的技术能力。 46 | 47 | # 技术栈 48 | 49 | * Vue:用于构建用户界面的 MVVM 框架 50 | * Vue-router:为单页面应用提供的路由系统 51 | * axios:用来请求后端数据,谁用谁知道 52 | * Vuex:集中状态管理,实现多个组件之间共享数据 53 | * SCSS:css 预编译处理器 54 | * ES6:新一代的语法规范 55 | 56 | # 其他工具 57 | * Vue-lazyload:实现图片的懒加载,优化页面性能节省用户流量 58 | * better-scroll:解决移动端各种滚动场景需求的插件,使移动端滑动体验更加流畅 59 | * fastcliclk:解决移动端300ms延迟 60 | * iconfont:阿里图标库 61 | * borderCSS:解决1像素边框问题,防止高分辨率手机将1px像素渲染成2或3单位物理像素 62 | * VueAwesomeSwiper:功能强大,可以实现不同效果的轮播功能 63 | 64 | 以上工具的使用以及配置,官方文档都有详细的描述,在这里就不赘述了。 65 | 66 | # 项目准备 67 | 68 | * 导入了reset.css,初始化了项目的一些基本样式。 69 | * 因为是移动app,所以需要在index.html里面需要对meta标签进行修改 70 | 71 | ```html 72 | 73 | ``` 74 | intial-scale:页面首次被显示是可视区域的缩放级别,取值1.0则页面按实际尺寸显示,无任何缩放 75 | maximum-scale=1.0, minimum-scale=1.0;可视区域的缩放级别, 76 | maximum-scale用户可将页面放大的程序,1.0将禁止用户放大到实际尺寸之上。 77 | user-scalable:是否可对页面进行缩放,no 禁止缩放 78 | 79 | # 实现功能 80 | 81 | ## 首页列表 82 | 83 | 基本都是使用axios获取数据,然后渲染到页面上。 84 | 85 | ## 城市列表页 86 | 87 | 搜索功能:在这里使用到了定时器实现函数节流,提高性能。 88 | 89 | 侧边栏拖拽页面联动:同样是使用到函数节流。主要思路是先获取字母A到顶部的距离,点击的时候触发事件,计算点击的点的坐标减去顶部的距离,获取到的数据除以每个字母的高度,然后向下取整,得到对应字母的索引,将数据传递给父组件再转发给列表显示组件,实现两个组件之间的联动。 90 | 91 | ## 城市选择 92 | 93 | 使用vuex传递城市选择页面和首页当前城市定位城市的数据,并且使用localStorage存储用户的选择,最好使用try catch将其包裹起来,防止浏览器关闭其功能导致代码无法运行。 94 | 95 | ## 详情页面 96 | 97 | 详情页面使用到了递归组件,如果需要渲染的数据中,存在children的子数组,需要显示多级的时候则需要使用到。这种场景算是第一次遇到,敲黑板记笔记了。 98 | 99 | # 学到了什么 100 | 101 | * 样式穿透:突破scope的限制,由于scope的限制,所以定义不起作用,使用/deep/标签可以突破scope的限制。终于找到之前修改无果的原因了。。。 102 | 103 | ```css 104 | .wrapper /deep/ .swiper-pagination-bullet-active{ 105 | background: #fff; 106 | } 107 | ``` 108 | 109 | * 防止页面抖动:给每一个页面的区域固定一个高度,防止网络问题,造成页面的抖动,padding-bottom的值主要是根据宽高比计算的。确实是JS体现技术深度,CSS体现经验程度 110 | 111 | ```css 112 | .wrapper{ 113 | overflow: hidden; 114 | background: #fff; 115 | width: 100%; 116 | height: 0; 117 | padding-bottom: 31.25%; 118 | } 119 | ``` 120 | 121 | * 网页的默认字体:设置默认字体为font-size为50px,那么在换算成为rem单位的时候,直接乘以0.02就可以了。这个可能不同人会有不同习惯,也有人设置成62.5%,也没有绝对的好坏之分。 122 | 如果有些方案无法具体确定下来的话,其实可以参考其他大厂的解决方案,因为用到的大都是前沿的、兼容性好点的技术,比如这个方案淘宝系和网易家的解决方案就不一样 123 | 124 | * 生命周期:这个说来是最羞耻的,平时学习以为只有常见的8个钩子函数,但是直到这里才发现漏了activated、deactivated以及vue2.5新增的errorCaptured。具体看官网文档吧,挺仔细的。 125 | 126 | # 写在最后 127 | 128 | 确实要养成一个看官方文档的好习惯,能够快速入门一项技术或工具,包括以上谈到的插件,官网也有很仔细的配置文档,这确实也是一个提高技术的重要手段,敲黑板敲黑板! 129 | 130 | 最后要感谢慕课网,DellLee老师的视频教程,确实学会挺多的。[传送门](https://coding.imooc.com/class/evaluation/203.html#Anchor) 131 | -------------------------------------------------------------------------------- /src/assets/styles/border.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | .border, 3 | .border-top, 4 | .border-right, 5 | .border-bottom, 6 | .border-left, 7 | .border-topbottom, 8 | .border-rightleft, 9 | .border-topleft, 10 | .border-rightbottom, 11 | .border-topright, 12 | .border-bottomleft { 13 | position: relative; 14 | } 15 | .border::before, 16 | .border-top::before, 17 | .border-right::before, 18 | .border-bottom::before, 19 | .border-left::before, 20 | .border-topbottom::before, 21 | .border-topbottom::after, 22 | .border-rightleft::before, 23 | .border-rightleft::after, 24 | .border-topleft::before, 25 | .border-topleft::after, 26 | .border-rightbottom::before, 27 | .border-rightbottom::after, 28 | .border-topright::before, 29 | .border-topright::after, 30 | .border-bottomleft::before, 31 | .border-bottomleft::after { 32 | content: "\0020"; 33 | overflow: hidden; 34 | position: absolute; 35 | } 36 | /* border 37 | * 因,边框是由伪元素区域遮盖在父级 38 | * 故,子级若有交互,需要对子级设置 39 | * 定位 及 z轴 40 | */ 41 | .border::before { 42 | box-sizing: border-box; 43 | top: 0; 44 | left: 0; 45 | height: 100%; 46 | width: 100%; 47 | border: 1px solid #eaeaea; 48 | transform-origin: 0 0; 49 | } 50 | .border-top::before, 51 | .border-bottom::before, 52 | .border-topbottom::before, 53 | .border-topbottom::after, 54 | .border-topleft::before, 55 | .border-rightbottom::after, 56 | .border-topright::before, 57 | .border-bottomleft::before { 58 | left: 0; 59 | width: 100%; 60 | height: 1px; 61 | } 62 | .border-right::before, 63 | .border-left::before, 64 | .border-rightleft::before, 65 | .border-rightleft::after, 66 | .border-topleft::after, 67 | .border-rightbottom::before, 68 | .border-topright::after, 69 | .border-bottomleft::after { 70 | top: 0; 71 | width: 1px; 72 | height: 100%; 73 | } 74 | .border-top::before, 75 | .border-topbottom::before, 76 | .border-topleft::before, 77 | .border-topright::before { 78 | border-top: 1px solid #eaeaea; 79 | transform-origin: 0 0; 80 | } 81 | .border-right::before, 82 | .border-rightbottom::before, 83 | .border-rightleft::before, 84 | .border-topright::after { 85 | border-right: 1px solid #eaeaea; 86 | transform-origin: 100% 0; 87 | } 88 | .border-bottom::before, 89 | .border-topbottom::after, 90 | .border-rightbottom::after, 91 | .border-bottomleft::before { 92 | border-bottom: 1px solid #eaeaea; 93 | transform-origin: 0 100%; 94 | } 95 | .border-left::before, 96 | .border-topleft::after, 97 | .border-rightleft::after, 98 | .border-bottomleft::after { 99 | border-left: 1px solid #eaeaea; 100 | transform-origin: 0 0; 101 | } 102 | .border-top::before, 103 | .border-topbottom::before, 104 | .border-topleft::before, 105 | .border-topright::before { 106 | top: 0; 107 | } 108 | .border-right::before, 109 | .border-rightleft::after, 110 | .border-rightbottom::before, 111 | .border-topright::after { 112 | right: 0; 113 | } 114 | .border-bottom::before, 115 | .border-topbottom::after, 116 | .border-rightbottom::after, 117 | .border-bottomleft::after { 118 | bottom: 0; 119 | } 120 | .border-left::before, 121 | .border-rightleft::before, 122 | .border-topleft::after, 123 | .border-bottomleft::before { 124 | left: 0; 125 | } 126 | @media (max--moz-device-pixel-ratio: 1.49), (-webkit-max-device-pixel-ratio: 1.49), (max-device-pixel-ratio: 1.49), (max-resolution: 143dpi), (max-resolution: 1.49dppx) { 127 | /* 默认值,无需重置 */ 128 | } 129 | @media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49), (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49), (min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49), (min-resolution: 144dpi) and (max-resolution: 239dpi), (min-resolution: 1.5dppx) and (max-resolution: 2.49dppx) { 130 | .border::before { 131 | width: 200%; 132 | height: 200%; 133 | transform: scale(.5); 134 | } 135 | .border-top::before, 136 | .border-bottom::before, 137 | .border-topbottom::before, 138 | .border-topbottom::after, 139 | .border-topleft::before, 140 | .border-rightbottom::after, 141 | .border-topright::before, 142 | .border-bottomleft::before { 143 | transform: scaleY(.5); 144 | } 145 | .border-right::before, 146 | .border-left::before, 147 | .border-rightleft::before, 148 | .border-rightleft::after, 149 | .border-topleft::after, 150 | .border-rightbottom::before, 151 | .border-topright::after, 152 | .border-bottomleft::after { 153 | transform: scaleX(.5); 154 | } 155 | } 156 | @media (min--moz-device-pixel-ratio: 2.5), (-webkit-min-device-pixel-ratio: 2.5), (min-device-pixel-ratio: 2.5), (min-resolution: 240dpi), (min-resolution: 2.5dppx) { 157 | .border::before { 158 | width: 300%; 159 | height: 300%; 160 | transform: scale(.33333); 161 | } 162 | .border-top::before, 163 | .border-bottom::before, 164 | .border-topbottom::before, 165 | .border-topbottom::after, 166 | .border-topleft::before, 167 | .border-rightbottom::after, 168 | .border-topright::before, 169 | .border-bottomleft::before { 170 | transform: scaleY(.33333); 171 | } 172 | .border-right::before, 173 | .border-left::before, 174 | .border-rightleft::before, 175 | .border-rightleft::after, 176 | .border-topleft::after, 177 | .border-rightbottom::before, 178 | .border-topright::after, 179 | .border-bottomleft::after { 180 | transform: scaleX(.33333); 181 | } 182 | } 183 | --------------------------------------------------------------------------------