├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── index.html ├── package.json ├── src ├── App.vue ├── assets │ ├── 2x │ │ ├── swiper1@2x.jpg │ │ ├── swiper2@2x.jpg │ │ ├── swiper3@2x.jpg │ │ └── vue@2x.jpg │ ├── 3x │ │ ├── swiper1@3x.jpg │ │ ├── swiper2@3x.jpg │ │ ├── swiper3@3x.jpg │ │ └── vue@3x.jpg │ ├── head.jpg │ ├── loading.gif │ ├── logo.png │ ├── select.png │ ├── transition.gif │ └── vue.jpg ├── base │ ├── css │ │ ├── _base.scss │ │ └── _normalize.scss │ └── js │ │ ├── api.js │ │ ├── base.js │ │ └── util.js ├── components │ ├── foot-nav.vue │ ├── loading.vue │ ├── pulling-word.vue │ ├── scroller.vue │ └── swiper.vue ├── main.js ├── pages │ ├── car.vue │ ├── classify.vue │ ├── film-detail.vue │ ├── film.vue │ ├── index.vue │ ├── login.vue │ ├── main.vue │ ├── search.vue │ └── vip-message.vue ├── router │ └── index.js └── store │ └── index.js ├── static └── .gitkeep └── webpack.mdown /.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-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # app 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var app = express() 12 | var webpack = require('webpack') 13 | var proxyMiddleware = require('http-proxy-middleware') 14 | var webpackConfig = require('./webpack.dev.conf') 15 | 16 | // default port where dev server listens for incoming traffic 17 | var port = process.env.PORT || config.dev.port 18 | // automatically open browser, if not set will be false 19 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 20 | // Define HTTP proxies to your custom API backend 21 | // https://github.com/chimurai/http-proxy-middleware 22 | var proxyTable = config.dev.proxyTable 23 | 24 | app.post("/login",function(req,res){ 25 | res.send({"code":"s"}) 26 | }) 27 | 28 | 29 | 30 | var compiler = webpack(webpackConfig) 31 | 32 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 33 | publicPath: webpackConfig.output.publicPath, 34 | quiet: true 35 | }) 36 | 37 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 38 | log: () => {} 39 | }) 40 | // force page reload when html-webpack-plugin template changes 41 | compiler.plugin('compilation', function (compilation) { 42 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 43 | hotMiddleware.publish({ action: 'reload' }) 44 | cb() 45 | }) 46 | }) 47 | 48 | // proxy api requests 49 | Object.keys(proxyTable).forEach(function (context) { 50 | var options = proxyTable[context] 51 | if (typeof options === 'string') { 52 | options = { target: options } 53 | } 54 | app.use(proxyMiddleware(options.filter || context, options)) 55 | }) 56 | 57 | // handle fallback for HTML5 history API 58 | app.use(require('connect-history-api-fallback')()) 59 | 60 | // serve webpack bundle output 61 | app.use(devMiddleware) 62 | 63 | // enable hot-reload and state-preserving 64 | // compilation error display 65 | app.use(hotMiddleware) 66 | 67 | // serve pure static assets 68 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 69 | app.use(staticPath, express.static('./static')) 70 | 71 | var uri = 'http://localhost:' + port 72 | 73 | var _resolve 74 | var readyPromise = new Promise(resolve => { 75 | _resolve = resolve 76 | }) 77 | 78 | console.log('> Starting dev server...') 79 | devMiddleware.waitUntilValid(() => { 80 | console.log('> Listening at ' + uri + '\n') 81 | // when env is testing, don't need open it 82 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 83 | opn(uri) 84 | } 85 | _resolve() 86 | }) 87 | 88 | var server = app.listen(port) 89 | 90 | module.exports = { 91 | ready: readyPromise, 92 | close: () => { 93 | server.close() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | '@': resolve('src'), 25 | 'components': resolve('src/components'), 26 | 'pages': resolve('src/pages'), 27 | } 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.vue$/, 33 | loader: 'vue-loader', 34 | options: vueLoaderConfig 35 | }, 36 | { 37 | test: /\.js$/, 38 | loader: 'babel-loader', 39 | include: [resolve('src'), resolve('test')] 40 | }, 41 | { 42 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 43 | loader: 'url-loader', 44 | options: { 45 | limit: 10000, 46 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 47 | } 48 | }, 49 | { 50 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 55 | } 56 | } 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | var AutoDllPlugin = require('autodll-webpack-plugin'); 12 | 13 | var env = config.build.env 14 | 15 | var webpackConfig = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ 18 | sourceMap: config.build.productionSourceMap, 19 | extract: true 20 | }) 21 | }, 22 | devtool: config.build.productionSourceMap ? '#source-map' : false, 23 | output: { 24 | path: config.build.assetsRoot, 25 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 26 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 27 | }, 28 | plugins: [ 29 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 30 | new webpack.DefinePlugin({ 31 | 'process.env': env 32 | }), 33 | new webpack.optimize.UglifyJsPlugin({ 34 | compress: { 35 | warnings: false 36 | }, 37 | sourceMap: true 38 | }), 39 | // extract css into its own file 40 | new ExtractTextPlugin({ 41 | filename: utils.assetsPath('css/[name].[contenthash].css') 42 | }), 43 | // Compress extracted CSS. We are using this plugin so that possible 44 | // duplicated CSS from different components can be deduped. 45 | new OptimizeCSSPlugin({ 46 | cssProcessorOptions: { 47 | safe: true 48 | } 49 | }), 50 | // generate dist index.html with correct asset hash for caching. 51 | // you can customize output by editing /index.html 52 | // see https://github.com/ampedandwired/html-webpack-plugin 53 | new HtmlWebpackPlugin({ 54 | filename: config.build.index, 55 | template: 'index.html', 56 | inject: true, 57 | minify: { 58 | removeComments: true, 59 | collapseWhitespace: true, 60 | removeAttributeQuotes: true 61 | // more options: 62 | // https://github.com/kangax/html-minifier#options-quick-reference 63 | }, 64 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 65 | chunksSortMode: 'dependency' 66 | }), 67 | // split vendor js into its own file 68 | new webpack.optimize.CommonsChunkPlugin({ 69 | name: 'vendor', 70 | minChunks: function (module, count) { 71 | // any required modules inside node_modules are extracted to vendor 72 | return ( 73 | module.resource && 74 | /\.js$/.test(module.resource) && 75 | module.resource.indexOf( 76 | path.join(__dirname, '../node_modules') 77 | ) === 0 78 | ) 79 | } 80 | }), 81 | // extract webpack runtime and module manifest to its own file in order to 82 | // prevent vendor hash from being updated whenever app bundle is updated 83 | new webpack.optimize.CommonsChunkPlugin({ 84 | name: 'manifest', 85 | chunks: ['vendor'] 86 | }), 87 | /* new AutoDllPlugin({ 88 | inject: true, // will inject the DLL bundle to index.html 89 | debug: true, 90 | filename: '[name]_[hash].js', 91 | path: './dll', 92 | entry: { 93 | vendor: [ 94 | 'vue','vuex','vue-router','axios','vue-lazyload','vue-touch','better-scroll' 95 | ] 96 | } 97 | }) ,*/ 98 | 99 | // copy custom static assets 100 | new CopyWebpackPlugin([ 101 | { 102 | from: path.resolve(__dirname, '../static'), 103 | to: config.build.assetsSubDirectory, 104 | ignore: ['.*'] 105 | } 106 | ]) 107 | ] 108 | }) 109 | 110 | if (config.build.productionGzip) { 111 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 112 | 113 | webpackConfig.plugins.push( 114 | new CompressionWebpackPlugin({ 115 | asset: '[path].gz[query]', 116 | algorithm: 'gzip', 117 | test: new RegExp( 118 | '\\.(' + 119 | config.build.productionGzipExtensions.join('|') + 120 | ')$' 121 | ), 122 | threshold: 10240, 123 | minRatio: 0.8 124 | }) 125 | ) 126 | } 127 | 128 | if (config.build.bundleAnalyzerReport) { 129 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 130 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 131 | } 132 | 133 | module.exports = webpackConfig 134 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8088, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app 6 | 7 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "linrun <411690557@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "better-scroll": "^1.2.6", 14 | "vue": "^2.3.3", 15 | "vue-awesome-swiper": "^2.5.4", 16 | "vue-lazyload": "^1.0.5", 17 | "vue-router": "^2.3.1", 18 | "vue-touch": "^2.0.0-beta.4", 19 | "vuex": "^2.3.1" 20 | }, 21 | "devDependencies": { 22 | "autodll-webpack-plugin": "^0.2.1", 23 | "autoprefixer": "^6.7.2", 24 | "axios": "^0.19.0", 25 | "babel-core": "^6.22.1", 26 | "babel-loader": "^6.2.10", 27 | "babel-plugin-transform-runtime": "^6.22.0", 28 | "babel-preset-env": "^1.3.2", 29 | "babel-preset-stage-2": "^6.22.0", 30 | "babel-register": "^6.22.0", 31 | "chalk": "^1.1.3", 32 | "connect-history-api-fallback": "^1.3.0", 33 | "copy-webpack-plugin": "^4.0.1", 34 | "css-loader": "^0.28.0", 35 | "eventsource-polyfill": "^0.9.6", 36 | "express": "^4.14.1", 37 | "extract-text-webpack-plugin": "^2.0.0", 38 | "file-loader": "^0.11.1", 39 | "friendly-errors-webpack-plugin": "^1.1.3", 40 | "html-webpack-plugin": "^2.28.0", 41 | "http-proxy-middleware": "^0.17.3", 42 | "iscroll": "^5.2.0", 43 | "node-sass": "^4.5.3", 44 | "opn": "^4.0.2", 45 | "optimize-css-assets-webpack-plugin": "^1.3.0", 46 | "ora": "^1.2.0", 47 | "rimraf": "^2.6.0", 48 | "sass-loader": "^6.0.6", 49 | "semver": "^5.3.0", 50 | "shelljs": "^0.7.6", 51 | "url-loader": "^0.5.8", 52 | "vue-loader": "^12.1.0", 53 | "vue-style-loader": "^3.0.1", 54 | "vue-template-compiler": "^2.3.3", 55 | "webpack": "^2.6.1", 56 | "webpack-bundle-analyzer": "^2.2.1", 57 | "webpack-dev-middleware": "^1.10.0", 58 | "webpack-hot-middleware": "^2.18.0", 59 | "webpack-merge": "^4.1.0" 60 | }, 61 | "engines": { 62 | "node": ">= 4.0.0", 63 | "npm": ">= 3.0.0" 64 | }, 65 | "browserslist": [ 66 | "> 1%", 67 | "last 2 versions", 68 | "not ie <= 8" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 14 | 42 | -------------------------------------------------------------------------------- /src/assets/2x/swiper1@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/2x/swiper1@2x.jpg -------------------------------------------------------------------------------- /src/assets/2x/swiper2@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/2x/swiper2@2x.jpg -------------------------------------------------------------------------------- /src/assets/2x/swiper3@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/2x/swiper3@2x.jpg -------------------------------------------------------------------------------- /src/assets/2x/vue@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/2x/vue@2x.jpg -------------------------------------------------------------------------------- /src/assets/3x/swiper1@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/3x/swiper1@3x.jpg -------------------------------------------------------------------------------- /src/assets/3x/swiper2@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/3x/swiper2@3x.jpg -------------------------------------------------------------------------------- /src/assets/3x/swiper3@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/3x/swiper3@3x.jpg -------------------------------------------------------------------------------- /src/assets/3x/vue@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/3x/vue@3x.jpg -------------------------------------------------------------------------------- /src/assets/head.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/head.jpg -------------------------------------------------------------------------------- /src/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/loading.gif -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/select.png -------------------------------------------------------------------------------- /src/assets/transition.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/transition.gif -------------------------------------------------------------------------------- /src/assets/vue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/src/assets/vue.jpg -------------------------------------------------------------------------------- /src/base/css/_base.scss: -------------------------------------------------------------------------------- 1 | @mixin fz($font-size) { 2 | font-size: $font-size; 3 | [data-dpr="2"] & { 4 | font-size: $font-size * 2; 5 | } 6 | [data-dpr="3"] & { 7 | font-size: $font-size * 3; 8 | } 9 | } 10 | @mixin dpr-img($url,$name,$type:".jpg"){ 11 | background-image: url($url+"2x/"+ $name+"@2x"+$type); 12 | [data-dpr="3"] &{ 13 | background-image: url($url+"3x/"+ $name+"@3x"+$type); 14 | } 15 | } 16 | 17 | 18 | 19 | @mixin table-center { 20 | display: table-cell; 21 | vertical-align: middle; 22 | text-align: center; 23 | } 24 | 25 | @mixin poa-center($w, $h) { 26 | position: absolute; 27 | width: $w; 28 | height: $h; 29 | left: 50%; 30 | top: 50%; 31 | // margin-left:-($w/2); 32 | // margin-top:-($h/2); 33 | transition: translate(-50%, -50%) 34 | } 35 | 36 | @mixin flex-center { 37 | display: flex; 38 | justify-content: center; 39 | align-items: center; 40 | } 41 | 42 | @mixin t-overflow($line:1) { 43 | @if $line==1 { 44 | overflow: hidden; 45 | text-overflow: ellipsis; 46 | white-space: nowrap; 47 | } 48 | @else { 49 | display: -webkit-box; 50 | -webkit-line-clamp: $line; 51 | -webkit-box-orient: vertical; 52 | overflow: hidden; 53 | text-overflow: ellipsis; 54 | } 55 | } 56 | 57 | @function torem($px) { 58 | @return $px / 75px * 1rem; 59 | } 60 | 61 | 62 | $mainColor:#77b59c; 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/base/css/_normalize.scss: -------------------------------------------------------------------------------- 1 | 2 | @charset "utf-8"; 3 | 4 | html{ 5 | 6 | color: #000; 7 | background: #fff; 8 | overflow-y: scroll; 9 | -webkit-text-size-adjust: 100%; 10 | -ms-text-size-adjust: 100%; 11 | 12 | } 13 | html * { 14 | outline: none; 15 | -webkit-text-size-adjust: none; 16 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 17 | } 18 | html, 19 | body { 20 | font-family: sans-serif; 21 | height: 100%; 22 | width:100%; 23 | // overflow:hidden; 24 | } 25 | 26 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote, 27 | th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section { 28 | margin: 0; 29 | padding: 0; 30 | } 31 | input, 32 | select, 33 | textarea { 34 | font-size: 100%; 35 | } 36 | 37 | table { 38 | border-collapse: collapse; 39 | border-spacing: 0; 40 | } 41 | 42 | fieldset, 43 | img { 44 | border: 0; 45 | } 46 | 47 | abbr, 48 | acronym { 49 | border: 0; 50 | font-variant: normal; 51 | } 52 | 53 | del { 54 | text-decoration: line-through; 55 | } 56 | address, 57 | caption, 58 | cite, 59 | code, 60 | dfn, 61 | em, 62 | th, 63 | i, 64 | var { 65 | font-style: normal; 66 | font-weight: 500; 67 | } 68 | 69 | ol, 70 | ul { 71 | list-style: none; 72 | } 73 | 74 | caption, 75 | th { 76 | text-align: left; 77 | } 78 | 79 | h1,h2,h3,h4,h5,h6 { 80 | font-size: 100%; 81 | font-weight: 500; 82 | } 83 | q:before, 84 | q:after { 85 | content: ''; 86 | } 87 | 88 | sub, 89 | sup { 90 | font-size: 75%; 91 | line-height: 0; 92 | position: relative; 93 | vertical-align: baseline; 94 | } 95 | sup { 96 | top: -0.5em; 97 | } 98 | sub { 99 | bottom: -0.25em; 100 | } 101 | 102 | a:hover { 103 | text-decoration: underline; 104 | } 105 | 106 | ins, 107 | a, 108 | a:active, 109 | a:visited, 110 | a:link{ 111 | text-decoration: none; 112 | } 113 | .clearfix{ 114 | &:after{ 115 | display: table; 116 | clear: both; 117 | content: ""; 118 | visibility: hidden;; 119 | height: 0; 120 | } 121 | } 122 | body{ 123 | height:100%; 124 | font-family: "Microsoft YaHei"; 125 | } 126 | 127 | -------------------------------------------------------------------------------- /src/base/js/api.js: -------------------------------------------------------------------------------- 1 | import {getDeviceRatio} from './util.js' 2 | export const api="https://api.douban.com/v2/movie/"; 3 | export var DEVICE_RATIO=getDeviceRatio(); 4 | 5 | export var DOWN_CONFIG={ 6 | threshold:80*DEVICE_RATIO, 7 | stop:40*DEVICE_RATIO 8 | } 9 | /*上拉配置*/ 10 | export var UP_CONFIG={ 11 | threshold:-80*DEVICE_RATIO, 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/base/js/base.js: -------------------------------------------------------------------------------- 1 | ;(function(win, lib) { 2 | var doc = win.document; 3 | var docEl = doc.documentElement; 4 | var metaEl = doc.querySelector('meta[name="viewport"]'); 5 | var flexibleEl = doc.querySelector('meta[name="flexible"]'); 6 | var dpr = 0; 7 | var scale = 0; 8 | var tid; 9 | var flexible = lib.flexible || (lib.flexible = {}); 10 | 11 | if (metaEl) { 12 | console.warn('将根据已有的meta标签来设置缩放比例'); 13 | var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/); 14 | if (match) { 15 | scale = parseFloat(match[1]); 16 | dpr = parseInt(1 / scale); 17 | } 18 | } else if (flexibleEl) { 19 | var content = flexibleEl.getAttribute('content'); 20 | if (content) { 21 | var initialDpr = content.match(/initial\-dpr=([\d\.]+)/); 22 | var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/); 23 | if (initialDpr) { 24 | dpr = parseFloat(initialDpr[1]); 25 | scale = parseFloat((1 / dpr).toFixed(2)); 26 | } 27 | if (maximumDpr) { 28 | dpr = parseFloat(maximumDpr[1]); 29 | scale = parseFloat((1 / dpr).toFixed(2)); 30 | } 31 | } 32 | } 33 | 34 | if (!dpr && !scale) { 35 | var isAndroid = win.navigator.appVersion.match(/android/gi); 36 | var isIPhone = win.navigator.appVersion.match(/iphone/gi); 37 | var devicePixelRatio = win.devicePixelRatio; 38 | if (isIPhone) { 39 | // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 40 | if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { 41 | dpr = 3; 42 | } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ 43 | dpr = 2; 44 | } else { 45 | dpr = 1; 46 | } 47 | } else { 48 | // 其他设备下,仍旧使用1倍的方案 49 | dpr = 1; 50 | } 51 | scale = 1 / dpr; 52 | } 53 | 54 | docEl.setAttribute('data-dpr', dpr); 55 | if (!metaEl) { 56 | metaEl = doc.createElement('meta'); 57 | metaEl.setAttribute('name', 'viewport'); 58 | metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); 59 | if (docEl.firstElementChild) { 60 | docEl.firstElementChild.appendChild(metaEl); 61 | } else { 62 | var wrap = doc.createElement('div'); 63 | wrap.appendChild(metaEl); 64 | doc.write(wrap.innerHTML); 65 | } 66 | } 67 | 68 | function refreshRem(){ 69 | var width = docEl.getBoundingClientRect().width; 70 | if (width / dpr > 540) { 71 | width = 540 * dpr; 72 | } 73 | var rem = width / 10; 74 | docEl.style.fontSize = rem + 'px'; 75 | flexible.rem = win.rem = rem; 76 | } 77 | 78 | win.addEventListener('resize', function() { 79 | clearTimeout(tid); 80 | tid = setTimeout(refreshRem, 300); 81 | }, false); 82 | win.addEventListener('pageshow', function(e) { 83 | if (e.persisted) { 84 | clearTimeout(tid); 85 | tid = setTimeout(refreshRem, 300); 86 | } 87 | }, false); 88 | 89 | if (doc.readyState === 'complete') { 90 | doc.body.style.fontSize = 12 * dpr + 'px'; 91 | } else { 92 | doc.addEventListener('DOMContentLoaded', function(e) { 93 | doc.body.style.fontSize = 12 * dpr + 'px'; 94 | }, false); 95 | } 96 | 97 | 98 | refreshRem(); 99 | 100 | flexible.dpr = win.dpr = dpr; 101 | flexible.refreshRem = refreshRem; 102 | flexible.rem2px = function(d) { 103 | var val = parseFloat(d) * this.rem; 104 | if (typeof d === 'string' && d.match(/rem$/)) { 105 | val += 'px'; 106 | } 107 | return val; 108 | } 109 | flexible.px2rem = function(d) { 110 | var val = parseFloat(d) / this.rem; 111 | if (typeof d === 'string' && d.match(/px$/)) { 112 | val += 'rem'; 113 | } 114 | return val; 115 | } 116 | 117 | })(window, window['lib'] || (window['lib'] = {})); -------------------------------------------------------------------------------- /src/base/js/util.js: -------------------------------------------------------------------------------- 1 | export function getStyle(el,style){ 2 | return parseInt(window.getComputedStyle(el, false)[style]) 3 | } 4 | 5 | export function getDeviceRatio(){ 6 | var isAndroid = window.navigator.appVersion.match(/android/gi); 7 | var isIPhone = window.navigator.appVersion.match(/iphone/gi); 8 | var devicePixelRatio = window.devicePixelRatio; 9 | var dpr; 10 | if (isIPhone) { 11 | // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 12 | if (devicePixelRatio >= 3) { 13 | dpr = 3; 14 | } else if (devicePixelRatio >= 2){ 15 | dpr = 2; 16 | } else { 17 | dpr = 1; 18 | } 19 | } else { 20 | // 其他设备下,仍旧使用1倍的方案 21 | dpr = 1; 22 | } 23 | return dpr 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/foot-nav.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 27 | 28 | 74 | -------------------------------------------------------------------------------- /src/components/loading.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/pulling-word.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/scroller.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 138 | 139 | -------------------------------------------------------------------------------- /src/components/swiper.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import router from './router' 4 | import './base/js/base.js' 5 | import { store } from './store/index.js' 6 | import 'swiper/dist/css/swiper.css' 7 | 8 | /*滑动事件*/ 9 | import VueTouch from 'vue-touch' 10 | Vue.use(VueTouch, { name: 'v-touch' }) 11 | 12 | /*ajax请求*/ 13 | import axios from 'axios' 14 | axios.defaults.baseURL = 'https://api.douban.com/v2/movie' 15 | Vue.prototype.$ajax = axios 16 | 17 | /*图片懒加载*/ 18 | import VueLazyload from 'vue-lazyload' 19 | Vue.use(VueLazyload, { 20 | preLoad: 1.3, 21 | loading: require('@/assets/head.jpg'), 22 | attempt: 1 23 | }) 24 | 25 | /* 轮播图 */ 26 | import VueAwesomeSwiper from 'vue-awesome-swiper' 27 | Vue.use(VueAwesomeSwiper) 28 | 29 | Vue.config.productionTip = false 30 | 31 | /* eslint-disable no-new */ 32 | new Vue({ 33 | el: '#app', 34 | router, 35 | store, 36 | render: h => h(App) 37 | }) 38 | -------------------------------------------------------------------------------- /src/pages/car.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 99 | 100 | 219 | -------------------------------------------------------------------------------- /src/pages/classify.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 215 | 216 | 315 | -------------------------------------------------------------------------------- /src/pages/film-detail.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 120 | 121 | -------------------------------------------------------------------------------- /src/pages/film.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 78 | 79 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 63 | 64 | -------------------------------------------------------------------------------- /src/pages/login.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 49 | 50 | 101 | -------------------------------------------------------------------------------- /src/pages/main.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /src/pages/search.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 135 | 136 | -------------------------------------------------------------------------------- /src/pages/vip-message.vue: -------------------------------------------------------------------------------- 1 | 98 | 99 | 120 | 121 | 292 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | const Index = () => import('pages/index') 5 | const Car = () => import('pages/car') 6 | const Search = () => import('pages/search') 7 | const Login = () => import('pages/login') 8 | const Main = () => import('pages/main') 9 | const vipMessage = () => import('pages/vip-message') 10 | const Classify = () => import('pages/classify') 11 | const FilmDetail = () => import('pages/film-detail') 12 | 13 | Vue.use(Router) 14 | 15 | export default new Router({ 16 | routes: [ 17 | { 18 | path: '', 19 | redirect: '/home' 20 | }, 21 | { 22 | path: '/home', 23 | component: Main, 24 | children: [ 25 | { 26 | path: '', 27 | redirect: 'index' 28 | }, 29 | 30 | { 31 | name: 'index', 32 | path: 'index', 33 | component: Index 34 | }, 35 | { 36 | name: 'search', 37 | path: 'search', 38 | component: Search 39 | }, 40 | { 41 | name: 'vip', 42 | path: 'vip', 43 | component: vipMessage 44 | } 45 | ] 46 | }, 47 | { 48 | path: '/classify/:type', 49 | component: Classify, 50 | name: 'classify' 51 | }, 52 | { 53 | path: '/login', 54 | component: Login 55 | }, 56 | { 57 | name: 'car', 58 | path: '/car', 59 | component: Car 60 | }, 61 | { 62 | path: '/film-detail/:id', 63 | component: FilmDetail 64 | } 65 | ] 66 | }) 67 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | Vue.use(Vuex); 4 | 5 | export const store= new Vuex.Store({ 6 | state:{ 7 | goodsList:localStorage["goodsList"]?JSON.parse(localStorage["goodsList"]): [] 8 | }, 9 | getters:{ 10 | sum:state=>{ 11 | var total=0; 12 | state.goodsList.forEach((item)=>{ 13 | if(item.select){ 14 | total+=item.price*item.number 15 | } 16 | }) 17 | return total 18 | }, 19 | goddsNumber:state=>{ 20 | return state.goodsList.length 21 | } 22 | }, 23 | mutations:{ 24 | addGoods:(state,data)=>{ 25 | state.goodsList.push(data); 26 | localStorage.setItem("goodsList",JSON.stringify(state.goodsList)); 27 | }, 28 | deleteGoods(state,index){ 29 | state.goodsList.splice(index,1); 30 | localStorage.setItem("goodsList",JSON.stringify(state.goodsList)); 31 | }, 32 | updateGoods(state,data){ 33 | const {index,key,value}=data; 34 | state.goodsList[index][key]=value; 35 | localStorage.setItem("goodsList",JSON.stringify(state.goodsList)); 36 | } 37 | } 38 | }) -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linrunzheng/vueApp/f3f4b0623f29cc106366a70b8dc4c797b0812398/static/.gitkeep -------------------------------------------------------------------------------- /webpack.mdown: -------------------------------------------------------------------------------- 1 | 上拉加载以及下拉刷新都是移动端很常见的功能,在搜索或者一些分类列表页面常常会用到。跟横向滚动一样,我们还是采用better-scroll这个库来实现。由于better已经更新了新的版本,之前是0.几的版本,更新了一下发现,现在已经是1.2.6这个版本了,新版本多了一个比较好用的api,所以我也重写了之前的代码,用新的api来实现上拉加载以及下拉刷新。 2 | 3 | 首先把列表基本的样式写好,这里就略过了,然后引入better-scroll库 4 | import BScroll from 'better-scroll' 5 | 6 | 其次,在mounted生命周期实例化scroll,可以获取完数据后再new,也可以先new后,获取完数据调用refresh()。实例时需要传入一个配置参数,由于参数比较多,具体的请参考文档,这里只讲2个重点的: 7 | 8 | 9 | //是否开启下拉刷新,可传入true或者false,如果需要更多配置可以传入一个对象 10 | /** 11 | * 12 | * @param threshold 触发事件的阀值,即滑动多少距离触发 13 | * @param stop 下拉刷新后回滚距离顶部的距离(为了给loading留出一点空间) 14 | */ 15 | 16 | pullDownRefresh:{ 17 | threshold:80, 18 | stop:40 19 | } 20 | 21 | //是否开启上拉加载,同上,上拉无stop参数,这里需要注意是负数 22 | pullUpLoad:{ 23 | threshold:-80, 24 | } 25 | 26 | 以上的数字个人感觉比较合适,但是这里有一个问题,由于我采用的是淘宝flexible.js来适配,这就导致:在安卓下80这个距离是合适的,但是到了iphone6s下,由于被缩放了3陪,所以现在80在iphone6s下就是27左右了,所以对于不同缩放程度的屏幕,还需要乘以对应的缩放比。 27 | 28 | 29 | 淘宝flexible.js里面其实已经有这个获取屏幕缩放比方法,这里直接从里面拿: 30 | 31 | export function getDeviceRatio(){ 32 | var isAndroid = window.navigator.appVersion.match(/android/gi); 33 | var isIPhone = window.navigator.appVersion.match(/iphone/gi); 34 | var devicePixelRatio = window.devicePixelRatio; 35 | var dpr; 36 | if (isIPhone) { 37 | // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 38 | if (devicePixelRatio >= 3) { 39 | dpr = 3; 40 | } else if (devicePixelRatio >= 2){ 41 | dpr = 2; 42 | } else { 43 | dpr = 1; 44 | } 45 | } else { 46 | // 其他设备下,仍旧使用1倍的方案 47 | dpr = 1; 48 | } 49 | return dpr 50 | } 51 | 52 | /*获取当前缩放比*/ 53 | const DEVICE_RATIO=getDeviceRatio(); 54 | 55 | 56 | /*下拉配置*/ 57 | const DOWN_CONFIG={ 58 | threshold:80*DEVICE_RATIO, 59 | stop:40*DEVICE_RATIO 60 | } 61 | /*上拉配置*/ 62 | const UP_CONFIG={ 63 | threshold:-80*DEVICE_RATIO, 64 | } 65 | 66 | this.scroller = new BScroll(scrollWrap,{ 67 | click:true, 68 | probeType:3, 69 | pullDownRefresh:DOWN_CONFIG, 70 | pullUpLoad:UP_CONFIG 71 | }); 72 | 73 | 实例化后,接下来就是监听上拉和下拉事件了。betterScroll新增了一些事件,主要的有: 74 | 75 | /*下拉刷新*/ 76 | this.scroller.on('pullingDown',()=> {}); 77 | 78 | /*上拉加载更多*/ 79 | this.scroller.on('pullingUp',()=>{}); 80 | 81 | 触发上拉或者下拉事件后,需要我们调用this.scroller.finishPullDown()或者this.scroller.finishPullUp()来通知better-scroll事件完成。 82 | 83 | this.scroller.on('pullingDown',()=> { 84 | 85 | 86 | 87 | }); 88 | 89 | 通常操作完成后都需要我们手动触发refresh方法来重新计算可滚动的距离,因此可以写一个watch监听数据的变化,这样我们只需要改变数据,不用每次操作数据后都调用refresh方法。 90 | 91 | 92 | watch:{ 93 | dataList(){ 94 | this.$nextTick(()=>{ 95 | this.scroller.refresh(); 96 | }) 97 | } 98 | }, 99 | 100 | 101 | 102 | 如果你使用的版本还是旧的,那可以在on( scroll )事件的时候进行判断来实现功能 103 | 104 | this.scroller.on("scroll",(pos)=>{ 105 | //获取整个滚动列表的高度 106 | var height=getStyle(scroller,"height"); 107 | 108 | //获取滚动外层wrap的高度 109 | var pageHeight=getStyle(scrollWrap,"height"); 110 | 111 | //触发事件需要的阀值 112 | var distance=80*DEVICE_RATIO; 113 | 114 | //参数pos为当前位置 115 | 116 | if(pos.y>distance){ 117 | if(this.pullUpflag){ 118 | //console.log("下拉"); 119 | //do something 120 | } 121 | 122 | }else if(pos.y-pageHeight<-height-distance){ 123 | //console.log("上拉"); 124 | //do something 125 | } 126 | 127 | 为了防止多次触发,需要加2个开关类的东西; 128 | var onPullUp=true; 129 | var onPullDown=true; 130 | 131 | 每次触发事件时,將对应的开关设置为false,等操作完成后,再重新设置为true,否则多次下拉或者上拉就会触发多次事件。通过设置开关可以保证每次只有一个事件在进行。 132 | 133 | 134 | 135 | 最后,来封装成一个组件 136 | 137 | 144 | 145 | 由于每个页面需要滚动的具体内容都是不一样的,所以用了一个插槽来分发。 146 | 147 | 148 | 组件需要的参数由父级传入,通过prop来接收并设置默认值 149 | 150 | export default { 151 | props: { 152 | dataList:{ 153 | type: Array, 154 | default: [] 155 | }, 156 | probeType: { 157 | type: Number, 158 | default: 3 159 | }, 160 | click: { 161 | type: Boolean, 162 | default: true 163 | }, 164 | pullDownRefresh: { 165 | type: null, 166 | default: false 167 | }, 168 | pullUpLoad: { 169 | type: null, 170 | default: false 171 | }, 172 | } 173 | 174 | 175 | 组件挂载后,在事件触发时并不直接处理事件,而是向父级发送一个事件,父级通过在模板v-on接收事件并处理后续的逻辑 176 | 177 | mounted() { 178 | this.scroll = new BScroll(this.$refs.wrapper, { 179 | probeType: this.probeType, 180 | click: this.click, 181 | pullDownRefresh: this.pullDownRefresh, 182 | pullUpLoad: this.pullUpLoad, 183 | }) 184 | 185 | this.scroll.on('pullingUp',()=> { 186 | if(this.continuePullUp){ 187 | this.beforePullUp(); 188 | this.$emit("onPullUp","当前状态:上拉加载"); 189 | } 190 | }); 191 | 192 | this.scroll.on('pullingDown',()=> { 193 | this.beforePullDown(); 194 | this.$emit("onPullDown","当前状态:下拉加载更多"); 195 | }); 196 | 197 | } 198 | 199 | 200 | 父组件在使用时,需要传入配置参数Props以及处理子组件发射的事件,并且用具体的内容并替换掉 slot 标签 201 | 210 | 211 | 225 | 226 | 227 | 228 | 229 | 230 | 完整的scroller组件内容如下 231 | 232 | 247 | 248 | 249 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | --------------------------------------------------------------------------------