├── static └── .gitkeep ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── src ├── assets │ └── styles │ │ ├── varibles.styl │ │ ├── mixins.styl │ │ ├── iconfont │ │ ├── iconfont.eot │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.svg │ │ ├── reset.css │ │ ├── iconfont.css │ │ └── border.css ├── store │ ├── mutations.js │ ├── state.js │ └── index.js ├── common │ ├── fade │ │ └── Fade.vue │ └── gallary │ │ └── Gallary.vue ├── App.vue ├── router │ └── index.js ├── main.js └── pages │ ├── city │ ├── components │ │ ├── Header.vue │ │ ├── Alphabet.vue │ │ ├── Search.vue │ │ └── List.vue │ └── City.vue │ ├── Detail │ ├── components │ │ ├── List.vue │ │ ├── Banner.vue │ │ └── Header.vue │ └── Detail.vue │ └── home │ ├── components │ ├── Swiper.vue │ ├── Weekend.vue │ ├── Header.vue │ ├── Recommend.vue │ └── Icons.vue │ └── Home.vue ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── .babelrc ├── index.html ├── test └── e2e │ ├── specs │ └── test.js │ ├── custom-assertions │ └── elementCount.js │ ├── nightwatch.conf.js │ └── runner.js ├── README.md └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/styles/varibles.styl: -------------------------------------------------------------------------------- 1 | $bgColor = #00bcd4 2 | $darkTextColor = #333 3 | $headerHeight = .86rem -------------------------------------------------------------------------------- /src/assets/styles/mixins.styl: -------------------------------------------------------------------------------- 1 | ellipsis() 2 | overflow: hidden 3 | white-space: nowrap 4 | text-overflow: ellipsis -------------------------------------------------------------------------------- /src/assets/styles/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcops/travel/HEAD/src/assets/styles/iconfont/iconfont.eot -------------------------------------------------------------------------------- /src/assets/styles/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcops/travel/HEAD/src/assets/styles/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/styles/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcops/travel/HEAD/src/assets/styles/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | changeCity (state,city) { 3 | state.city = city 4 | try { 5 | localStorage = city 6 | } catch (e) {} 7 | } 8 | } -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 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/store/state.js: -------------------------------------------------------------------------------- 1 | let defaultcity = '上海' 2 | try { 3 | if (localStorage.city) { 4 | defaultcity = localStorage 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 | /test/e2e/reports/ 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | my-project 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/common/fade/Fade.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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 | export default new Vuex.Store({ 8 | state: state, 9 | actions: { 10 | changeCity (ctx,city) { 11 | ctx.commit('changeCity',city) 12 | } 13 | }, 14 | mutations: mutations, 15 | getters: { 16 | doubleCity (state) { 17 | return state.city + ' ' + state.city 18 | 19 | } 20 | } 21 | 22 | }) -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | import Detail from '@/pages/detail/Detail' 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [{ 10 | path: '/', 11 | name: 'Home', 12 | component: () => import('@/pages/home/Home') 13 | }, 14 | { 15 | path: '/city', 16 | name: 'City', 17 | component: () => import('@/pages/city/City') 18 | }, 19 | { 20 | path: '/detail/:id', 21 | name: 'Detail', 22 | component: () => import('@/pages/detail/Detail') 23 | }, 24 | ], 25 | scrollBehavior (to, from, savedPosition) { 26 | return { x: 0, y: 0 } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /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 'babel-polyfill' 7 | import store from './store' 8 | //导入 300毫秒点击事件延迟问题 9 | import fastClick from 'fastclick' 10 | // 导入轮播插件 11 | import VueAwesomeSwiper from 'vue-awesome-swiper' 12 | import 'swiper/dist/css/swiper.css' 13 | 14 | // 导入多设备访问 显示问题一致性css 15 | import 'styles/reset.css' 16 | // 导入 解决一像素边框问题 17 | import 'styles/border.css' 18 | import 'styles/iconfont.css' 19 | 20 | 21 | 22 | fastClick.attach(document.body) 23 | Vue.config.productionTip = false 24 | 25 | Vue.use(VueAwesomeSwiper) 26 | /* eslint-disable no-new */ 27 | new Vue({ 28 | el: '#app', 29 | router, 30 | store, 31 | components: { App }, 32 | template: '' 33 | }) 34 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/pages/city/components/Header.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/pages/Detail/components/List.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 26 | 27 | -------------------------------------------------------------------------------- /src/pages/home/components/Swiper.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | 37 | -------------------------------------------------------------------------------- /src/pages/home/components/Weekend.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 28 | 29 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 去哪网APP 2 | 3 | 👉 项目演示地址:http://118.25.39.84 4 | 5 | 基于 **Vue 全家桶 (2.x)** 制作的 6 | 去哪网APP项目,项目完整、功能完备、UI美观、交互一流。 7 | 8 | ## [点击查看效果](http://118.25.39.84/sp.mp4) 9 | 10 | ## 技术栈 11 | 12 | ### 【前端】 13 | 14 | - **Vue**:构建用户界面的 MVVM 框架,核心是响应的数据绑定和组系统件 15 | - **vue-router**:为单页面应用提供的路由系统,项目上线前使用了路由懒加载技术,来异步加载路由优化性能 16 | - **vuex**:Vue 集中状态管理,在多个组件共享某些状态时非常便捷 17 | - **axios**:服务端通讯。基于 `Promise` 的网络请求插件 18 | - **vue-lazyload**:第三方图片懒加载库,优化页面加载速度 19 | - **better-scroll**:iscroll 的优化版,使移动端滑动体验更加流畅 20 | - **stylus**:css 预编译处理器 21 | - **ES6**:ECMAScript 22 | - **vue-awesome-swiper**: 图片轮播插件 23 | - **fastClick**: 解决300毫秒点击事件延迟问题 24 | - **stylus-loader**: 一像素边框问题 25 | 新一代语法,模块化、解构赋值、Promise、Class 等方法非常好用 26 | 27 | 28 | ### 【后端数据】 29 | 30 | - 使用本地模拟数据 31 | 32 | 33 | ### 【自动化构建及其他工具】 34 | 35 | - **vue-cli**:Vue 脚手架工具,快速初始化项目代码 36 | - **ESLint**:代码风格检查工具,规范代码书写 37 | 38 | 39 | ## 收获 40 | 41 | 1. 对 vue 的组件、指令、选项、模版渲染、事件绑定、计算属性等有了一定了解 42 | 2. 了解了 vue 组件之间的交互、传值 43 | 3. 熟悉了在 vue 项目中使用第三方插件(组件) 44 | 4. 熟悉了组件化、模块化的开发思维 45 | 5. 熟悉了 vue-router 控制路由和子路由的方式 46 | 6. 再次熟悉项目开发流程:项目分析设计 -> 项目环境搭建 -> 依赖安装 -> 页面架构设计 -> 组件开发 -> 测试联调 -> 发布上线 47 | 4. 体会到组件化、模块化开发带来的便捷 48 | 5. 体会到将对象封装成类(ES6 class) 的便捷性,以及利用工厂方式初始化类实例 49 | 6. 学会利用过渡效果及动画效果制作良好的用户交互体验 50 | 51 | 52 | 53 | ## Build Setup 54 | 55 | ``` bash 56 | # clone the repo into your disk. 57 | $ git clone https://github.com/jcops/travel.git 58 | 59 | # install dependencies 60 | $ npm install 61 | 62 | # serve with hot reload at localhost:8080 63 | $ npm run dev 64 | 65 | # build for production with minification 66 | $ npm run build 67 | ``` 68 | 69 | 70 | ## License 71 | 72 | The code is available under the [MIT license](https://opensource.org/licenses/MIT). 73 | 74 | ![](http://oph264zoo.bkt.clouddn.com/17-8-11/10545126.jpg) 75 | -------------------------------------------------------------------------------- /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: #25a4bb;text-decoration: none;} 26 | -------------------------------------------------------------------------------- /src/common/gallary/Gallary.vue: -------------------------------------------------------------------------------- 1 | 15 | 44 | 45 | -------------------------------------------------------------------------------- /src/pages/home/components/Header.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 30 | 31 | -------------------------------------------------------------------------------- /src/pages/city/City.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/pages/Detail/Detail.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 61 | 62 | -------------------------------------------------------------------------------- /src/pages/home/components/Recommend.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /src/pages/home/Home.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 73 | 74 | -------------------------------------------------------------------------------- /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://127.0.0.1', 16 | // pathRewrite: { 17 | // '^/api': '/static/mock' 18 | // } 19 | } 20 | }, 21 | 22 | // Various Dev Server settings 23 | host: '0.0.0.0', // can be overwritten by process.env.HOST 24 | port: 80, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 25 | autoOpenBrowser: false, 26 | errorOverlay: true, 27 | notifyOnErrors: true, 28 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 29 | 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-project", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "e2e": "node test/e2e/runner.js", 11 | "test": "npm run e2e", 12 | "build": "node build/build.js" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.18.0", 16 | "babel-polyfill": "^6.26.0", 17 | "better-scroll": "^1.12.5", 18 | "fastclick": "^1.0.6", 19 | "stylus": "^0.54.5", 20 | "stylus-loader": "^3.0.2", 21 | "vue": "^2.5.2", 22 | "vue-awesome-swiper": "^2.6.7", 23 | "vue-router": "^3.0.1", 24 | "vuex": "^3.0.1" 25 | }, 26 | "devDependencies": { 27 | "autoprefixer": "^7.1.2", 28 | "babel-core": "^6.22.1", 29 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 30 | "babel-loader": "^7.1.1", 31 | "babel-plugin-syntax-jsx": "^6.18.0", 32 | "babel-plugin-transform-runtime": "^6.22.0", 33 | "babel-plugin-transform-vue-jsx": "^3.5.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "babel-register": "^6.22.0", 37 | "chalk": "^2.0.1", 38 | "chromedriver": "^2.27.2", 39 | "copy-webpack-plugin": "^4.0.1", 40 | "cross-spawn": "^5.0.1", 41 | "css-loader": "^0.28.0", 42 | "extract-text-webpack-plugin": "^3.0.0", 43 | "file-loader": "^1.1.4", 44 | "friendly-errors-webpack-plugin": "^1.6.1", 45 | "html-webpack-plugin": "^2.30.1", 46 | "nightwatch": "^0.9.12", 47 | "node-notifier": "^5.1.2", 48 | "optimize-css-assets-webpack-plugin": "^3.2.0", 49 | "ora": "^1.2.0", 50 | "portfinder": "^1.0.13", 51 | "postcss-import": "^11.0.0", 52 | "postcss-loader": "^2.0.8", 53 | "postcss-url": "^7.2.1", 54 | "rimraf": "^2.6.0", 55 | "selenium-server": "^3.0.1", 56 | "semver": "^5.3.0", 57 | "shelljs": "^0.7.6", 58 | "uglifyjs-webpack-plugin": "^1.1.1", 59 | "url-loader": "^0.5.8", 60 | "vue-loader": "^13.3.0", 61 | "vue-style-loader": "^3.0.1", 62 | "vue-template-compiler": "^2.5.2", 63 | "webpack": "^3.6.0", 64 | "webpack-bundle-analyzer": "^2.9.0", 65 | "webpack-dev-server": "^2.9.1", 66 | "webpack-merge": "^4.1.0" 67 | }, 68 | "engines": { 69 | "node": ">= 6.0.0", 70 | "npm": ">= 3.0.0" 71 | }, 72 | "browserslist": [ 73 | "> 1%", 74 | "last 2 versions", 75 | "not ie <= 8" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /src/pages/Detail/components/Banner.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | -------------------------------------------------------------------------------- /src/pages/home/components/Icons.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 47 | 48 | -------------------------------------------------------------------------------- /src/pages/city/components/Alphabet.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/pages/Detail/components/Header.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 59 | 60 | -------------------------------------------------------------------------------- /src/assets/styles/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('./iconfont/iconfont.eot?t=1531559045212'); /* IE9*/ 4 | src: url('./iconfont/iconfont.eot?t=1531559045212#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'), 6 | url('./iconfont/iconfont.ttf?t=1531559045212') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('./iconfont/iconfont.svg?t=1531559045212#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | /*.icon-jiantou:before { content: "\e64a"; } 19 | 20 | .icon-fanhui:before { content: "\e624"; } 21 | 22 | .icon-sousuo:before { content: "\e632"; }*/ 23 | 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/pages/city/components/Search.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/pages/city/components/List.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------