├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .vscode └── settings.json ├── README ├── README.md ├── build ├── build.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── gulpfile.js ├── index.html ├── package.json ├── src ├── assets │ ├── css │ │ ├── common.css │ │ └── m.css │ ├── images │ │ └── wechat.png │ └── util.js ├── components │ └── Modal.vue ├── config.js ├── filters │ ├── dateFormatter.js │ └── index.js ├── m │ └── login │ │ ├── app.vue │ │ ├── login.css │ │ ├── login.html │ │ └── login.js ├── module │ └── login │ │ ├── app.vue │ │ ├── login.css │ │ ├── login.html │ │ └── login.js └── services │ ├── service.js │ └── xhr │ ├── config.js │ ├── index.js │ └── vueResource.js └── static └── .gitkeep /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 8 | extends: 'standard', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'html' 12 | ], 13 | // add your custom rules here 14 | 'rules': { 15 | // allow paren-less arrow functions 16 | 'arrow-parens': 0, 17 | // allow async-await 18 | 'generator-star-spacing': 0, 19 | // allow debugger during development 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | test/unit/coverage 6 | 7 | .idea/.name 8 | .idea/compiler.xml 9 | .idea/copyright/profiles_settings.xml 10 | .idea/encodings.xml 11 | .idea/merchant-web.iml 12 | .idea/misc.xml 13 | .idea/modules.xml 14 | .idea/vcs.xml 15 | .idea/workspace.xml 16 | .idea/inspectionProfiles/profiles_settings.xml 17 | .idea/inspectionProfiles/Project_Default.xml 18 | .idea/jsLibraryMappings.xml 19 | .idea/jsLinters/jshint.xml 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false 3 | } -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawnyu/vue-cli-multipage/fd279916e977537e33f48ba517e3b6636d254f27/README -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ----- 2 | 基于vue-cli单页应用改成多页应用 3 | ----- 4 | #### git地址 5 | `https://github.com/dawnyu/vue-cli-multipage.git` 6 | ### 前言 7 | 从接触vue开始用的是vue-cli直接搭建单页应用,参考配合着vue-router开发起来简直爽到吊炸天,但是由于项目越来越复杂了,单页用起来可能有点力不从心,能不能弄成多页面呢,查了相关资料得到的结论是完全可以的,能多页面多入口,并且可以使用组件,还引入jQuery,这简直完美了,这个demo是从我已经改造完成的项目中摘出来的,现在演示下怎么把基于vue2的vue-cli单页模板改造成多页面,并且多入口的项目。 8 | ### 技术栈 9 | * **vue: 2.0.1** 10 | * **vue-resource:1.0.3** 11 | * **vue-router:2.0.0** 12 | * **webpack:1.13.2** 13 | * **gulp:3.9.1** 14 | * **ES6** 15 | 16 | ### 运行 17 | ``` 18 | git clone https://github.com/dawnyu/vue-cli-multipage.git 19 | npm install 20 | npm run build 21 | npm run dev 22 | ``` 23 | 24 | ### 改造后的目录 25 | ![](http://ome9tqeyq.bkt.clouddn.com/vue-mult2.png) 26 | 可以多目录生成目标文件 27 | 28 | 公共的js和样式图标放到assets文件夹即可 29 | 30 | ### 修改点 31 | 32 | **build/utils.js** 33 | ```js 34 | var path = require('path') 35 | var config = require('../config') 36 | var glob = require('glob') 37 | // 将样式提取到单独的css文件中,而不是打包到js文件或使用style标签插入在head标签中 38 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 39 | 40 | exports.assetsPath = function(_path) { 41 | var assetsSubDirectory = process.env.NODE_ENV === 'production' ? 42 | config.build.assetsSubDirectory : 43 | config.dev.assetsSubDirectory 44 | return path.posix.join(assetsSubDirectory, _path) 45 | } 46 | 47 | exports.cssLoaders = function(options) { 48 | options = options || {} 49 | // generate loader string to be used with extract text plugin 50 | function generateLoaders(loaders) { 51 | var sourceLoader = loaders.map(function(loader) { 52 | var extraParamChar 53 | if (/\?/.test(loader)) { 54 | loader = loader.replace(/\?/, '-loader?') 55 | extraParamChar = '&' 56 | } else { 57 | loader = loader + '-loader' 58 | extraParamChar = '?' 59 | } 60 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 61 | }).join('!') 62 | 63 | if (options.extract) { 64 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 65 | } else { 66 | return ['vue-style-loader', sourceLoader].join('!') 67 | } 68 | } 69 | 70 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 71 | return { 72 | css: generateLoaders(['css']), 73 | postcss: generateLoaders(['css']), 74 | less: generateLoaders(['css', 'less']), 75 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 76 | scss: generateLoaders(['css', 'sass']), 77 | stylus: generateLoaders(['css', 'stylus']), 78 | styl: generateLoaders(['css', 'stylus']) 79 | } 80 | } 81 | 82 | // Generate loaders for standalone style files (outside of .vue) 83 | exports.styleLoaders = function(options) { 84 | var output = [] 85 | var loaders = exports.cssLoaders(options) 86 | for (var extension in loaders) { 87 | var loader = loaders[extension] 88 | output.push({ 89 | test: new RegExp('\\.' + extension + '$'), 90 | loader: loader 91 | }) 92 | } 93 | return output 94 | } 95 | //增加获取多入口的方法 注意 这个参数是个数组 96 | exports.getEntry = function(globPaths) { 97 | var entries = {}, 98 | basename, tmp, pathname; 99 | for (globPath of globPaths) { 100 | glob.sync(globPath).forEach(function(entry) { 101 | basename = path.basename(entry, path.extname(entry)); 102 | tmp = entry.split('/').splice(-3); 103 | pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径 104 | entries[pathname] = entry; 105 | }); 106 | } 107 | console.log(entries); 108 | return entries; 109 | } 110 | ``` 111 | **webpack.base.conf.js** 112 | ```js 113 | var path = require('path') 114 | var config = require('../config') 115 | var webpack = require('webpack') 116 | var merge = require('webpack-merge') 117 | var utils = require('./utils') 118 | var projectRoot = path.resolve(__dirname, '../') ///——driname当前目录 119 | var chunks = Object.keys(utils.getEntry(['./src/module/**/*.js', './src/m/**/*.js'])); 120 | // 将样式提取到单独的css文件中,而不是打包到js文件或使用style标签插入在head标签中 121 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 122 | module.exports = { 123 | entry: utils.getEntry(['./src/module/**/*.js', './src/m/**/*.js']),//传入需要打包的入口,我这里是pc端和手机端入口打到一个包里 124 | output: { 125 | path: config.build.assetsRoot, 126 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, //根名称可配置 127 | filename: '[name].js' 128 | }, 129 | resolve: { 130 | extensions: ['', '.js', '.vue'], 131 | fallback: [path.join(__dirname, '../node_modules')], 132 | alias: { 133 | 'src': path.resolve(__dirname, '../src'), 134 | 'assets': path.resolve(__dirname, '../src/assets'), 135 | 'components': path.resolve(__dirname, '../src/components'), 136 | 'jquery': 'jquery' 137 | } 138 | }, 139 | resolveLoader: { 140 | fallback: [path.join(__dirname, '../node_modules')] 141 | }, 142 | module: { 143 | loaders: [{ 144 | test: /\.vue$/, 145 | loader: 'vue-loader' 146 | }, 147 | { 148 | test: /\.js$/, 149 | loader: 'babel', 150 | include: projectRoot, 151 | exclude: /node_modules/ 152 | }, 153 | { 154 | test: /\.json$/, 155 | loader: 'json' 156 | }, 157 | { 158 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 159 | loader: 'url', 160 | query: { 161 | limit: 30000, 162 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 163 | } 164 | }, 165 | { 166 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 167 | loader: 'url', 168 | query: { 169 | limit: 10000, 170 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 171 | } 172 | } 173 | ] 174 | }, 175 | eslint: { 176 | formatter: require('eslint-friendly-formatter') 177 | }, 178 | vue: { 179 | loaders: utils.cssLoaders(), 180 | postcss: [ 181 | require('autoprefixer')({ 182 | browsers: ['last 2 versions'] 183 | }) 184 | ] 185 | }, 186 | plugins: [ 187 | // new webpack.optimize.CommonsChunkPlugin('static/build.js'), 188 | // 提取公共模块 189 | new webpack.optimize.CommonsChunkPlugin({ 190 | name: 'vendors', // 公共模块的名称 191 | chunks: chunks, // chunks是需要提取的模块 192 | minChunks: chunks.length 193 | }), 194 | // 配置提取出的样式文件 195 | new ExtractTextPlugin('css/[name].css'), 196 | //引入jqury 197 | new webpack.ProvidePlugin({ 198 | $: "jquery", 199 | jQuery: "jquery" 200 | }) 201 | ], 202 | } 203 | ``` 204 | 205 | **webpack.dev.conf.js** 206 | ```js 207 | var config = require('../config') 208 | var webpack = require('webpack') 209 | var merge = require('webpack-merge') 210 | var utils = require('./utils') 211 | var baseWebpackConfig = require('./webpack.base.conf') 212 | var HtmlWebpackPlugin = require('html-webpack-plugin') 213 | // add hot-reload related code to entry chunks 214 | Object.keys(baseWebpackConfig.entry).forEach(function(name) { 215 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 216 | }) 217 | 218 | module.exports = merge(baseWebpackConfig, { 219 | module: { 220 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 221 | }, 222 | // eval-source-map is faster for development 223 | devtool: '#eval-source-map', 224 | plugins: [ 225 | new webpack.DefinePlugin({ 226 | 'process.env': config.dev.env 227 | }), 228 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 229 | new webpack.optimize.OccurenceOrderPlugin(), 230 | new webpack.HotModuleReplacementPlugin(), 231 | new webpack.NoErrorsPlugin(), 232 | // https://github.com/ampedandwired/html-webpack-plugin 233 | // new HtmlWebpackPlugin({ 234 | // filename: 'index.html', 235 | // template: 'index.html', 236 | // inject: true 237 | // }) 238 | ] 239 | }) 240 | 241 | var pages = utils.getEntry(['./src/module/**/*.html', './src/m/**/*.html']); 242 | 243 | 244 | for (var pathname in pages) { 245 | 246 | 247 | // 配置生成的html文件,定义路径等 248 | var conf = { 249 | filename: pathname + '.html', 250 | template: pages[pathname], // 模板路径 251 | favicon: './src/assets/images/wechat.png', 252 | inject: true // js插入位置 253 | 254 | }; 255 | 256 | 257 | if (pathname in module.exports.entry) { 258 | conf.chunks = ['vendors', pathname]; 259 | conf.hash = true; 260 | } 261 | 262 | module.exports.plugins.push(new HtmlWebpackPlugin(conf)); 263 | } 264 | ``` 265 | 266 | **webpack.prod.conf.js** 267 | ```js 268 | var path = require('path') 269 | var config = require('../config') 270 | var utils = require('./utils') 271 | var webpack = require('webpack') 272 | var merge = require('webpack-merge') 273 | var baseWebpackConfig = require('./webpack.base.conf') 274 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 275 | var HtmlWebpackPlugin = require('html-webpack-plugin') 276 | var env = process.env.NODE_ENV === 'testing' ? 277 | require('../config/test.env') : 278 | config.build.env 279 | 280 | module.exports = merge(baseWebpackConfig, { 281 | module: { 282 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) 283 | }, 284 | devtool: config.build.productionSourceMap ? '#source-map' : false, 285 | output: { 286 | path: config.build.assetsRoot, 287 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 288 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 289 | }, 290 | vue: { 291 | loaders: utils.cssLoaders({ 292 | sourceMap: config.build.productionSourceMap, 293 | extract: true 294 | }) 295 | }, 296 | plugins: [ 297 | // http://vuejs.github.io/vue-loader/workflow/production.html 298 | new webpack.DefinePlugin({ 299 | 'process.env': env 300 | }), 301 | new webpack.optimize.UglifyJsPlugin({ 302 | compress: { 303 | warnings: false, 304 | drop_debugger: true, 305 | drop_console: true 306 | } 307 | }), 308 | new webpack.optimize.OccurenceOrderPlugin(), 309 | // extract css into its own file 310 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 311 | // generate dist index.html with correct asset hash for caching. 312 | // you can customize output by editing /index.html 313 | // see https://github.com/ampedandwired/html-webpack-plugin 314 | // new HtmlWebpackPlugin({ 315 | // filename: process.env.NODE_ENV === 'testing' ? 316 | // 'index.html' : config.build.index, 317 | // template: 'index.html', 318 | // favicon: './src/assets/images/tjd.ico', 319 | // inject: true, 320 | // minify: { 321 | // removeComments: true, 322 | // collapseWhitespace: true, 323 | // removeAttributeQuotes: true 324 | // // more options: 325 | // // https://github.com/kangax/html-minifier#options-quick-reference 326 | // }, 327 | // // necessary to consistently work with multiple chunks via CommonsChunkPlugin 328 | // chunksSortMode: 'dependency' 329 | // }), 330 | // split vendor js into its own file 331 | new webpack.optimize.CommonsChunkPlugin({ 332 | name: 'vendor', 333 | minChunks: function(module, count) { 334 | // any required modules inside node_modules are extracted to vendor 335 | return ( 336 | module.resource && 337 | /\.js$/.test(module.resource) && 338 | module.resource.indexOf( 339 | path.join(__dirname, '../node_modules') 340 | ) === 0 341 | ) 342 | } 343 | }), 344 | // extract webpack runtime and module manifest to its own file in order to 345 | // prevent vendor hash from being updated whenever app bundle is updated 346 | new webpack.optimize.CommonsChunkPlugin({ 347 | name: 'manifest', 348 | chunks: ['vendor'] 349 | }) 350 | ] 351 | }) 352 | 353 | if (config.build.productionGzip) { 354 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 355 | 356 | webpackConfig.plugins.push( 357 | new CompressionWebpackPlugin({ 358 | asset: '[path].gz[query]', 359 | algorithm: 'gzip', 360 | test: new RegExp( 361 | '\\.(' + 362 | config.build.productionGzipExtensions.join('|') + 363 | ')$' 364 | ), 365 | threshold: 10240, 366 | minRatio: 0.8 367 | }) 368 | ) 369 | } 370 | 371 | var pages = utils.getEntry(['./src/module/**/*.html', './src/m/**/*.html']); 372 | 373 | for (var pathname in pages) { 374 | 375 | 376 | // 配置生成的html文件,定义路径等 377 | var conf = { 378 | filename: pathname + '.html', 379 | template: pages[pathname], // 模板路径 380 | favicon: './src/assets/images/wechat.png', 381 | inject: true // js插入位置 382 | 383 | }; 384 | if (pathname in pages) { 385 | conf.chunks = ['vendors', pathname]; 386 | conf.hash = true; 387 | } 388 | 389 | module.exports.plugins.push(new HtmlWebpackPlugin(conf)); 390 | } 391 | ``` 392 | 393 | #### 具体怎么使用当可以查看下代码中使用 394 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | //打包文件 命令 npm run build 3 | require('shelljs/global') 4 | env.NODE_ENV = 'production' 5 | 6 | var path = require('path') 7 | var config = require('../config') 8 | var ora = require('ora') //Elegant terminal spinner 终端 9 | var webpack = require('webpack') 10 | var webpackConfig = require('./webpack.prod.conf') 11 | 12 | console.log( 13 | ' Tip:\n' + 14 | ' Built files are meant to be served over an HTTP server.\n' + 15 | ' Opening index.html over file:// won\'t work.\n' 16 | ) 17 | 18 | var spinner = ora('building for production...') 19 | spinner.start() 20 | 21 | var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 22 | rm('-rf', assetsPath) 23 | mkdir('-p', assetsPath) 24 | cp('-R', 'static/*', assetsPath) 25 | 26 | webpack(webpackConfig, function (err, stats) { 27 | spinner.stop() 28 | if (err) throw err 29 | process.stdout.write(stats.toString({ 30 | colors: true, 31 | modules: false, 32 | children: false, 33 | chunks: false, 34 | chunkModules: false 35 | }) + '\n') 36 | }) 37 | -------------------------------------------------------------------------------- /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 | var path = require('path') 2 | var express = require('express') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var opn = require('opn') 6 | var proxyMiddleware = require('http-proxy-middleware') 7 | var webpackConfig = process.env.NODE_ENV === 'testing' 8 | ? require('./webpack.prod.conf') 9 | : require('./webpack.dev.conf') 10 | 11 | // default port where dev server listens for incoming traffic 12 | var port = process.env.PORT || config.dev.port 13 | // Define HTTP proxies to your custom API backend 14 | // https://github.com/chimurai/http-proxy-middleware 15 | var proxyTable = config.dev.proxyTable 16 | 17 | var app = express() 18 | var compiler = webpack(webpackConfig) 19 | 20 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 21 | publicPath: webpackConfig.output.publicPath, 22 | stats: { 23 | colors: true, 24 | chunks: false 25 | } 26 | }) 27 | 28 | var hotMiddleware = require('webpack-hot-middleware')(compiler) 29 | // force page reload when html-webpack-plugin template changes 30 | compiler.plugin('compilation', function (compilation) { 31 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 32 | hotMiddleware.publish({ action: 'reload' }) 33 | cb() 34 | }) 35 | }) 36 | 37 | // proxy api requests 38 | Object.keys(proxyTable).forEach(function (context) { 39 | var options = proxyTable[context] 40 | if (typeof options === 'string') { 41 | options = { target: options } 42 | } 43 | app.use(proxyMiddleware(context, options)) 44 | }) 45 | 46 | // handle fallback for HTML5 history API 47 | app.use(require('connect-history-api-fallback')()) 48 | 49 | // serve webpack bundle output 50 | app.use(devMiddleware) 51 | 52 | // enable hot-reload and state-preserving 53 | // compilation error display 54 | app.use(hotMiddleware) 55 | 56 | // serve pure static assets 57 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 58 | app.use(staticPath, express.static('./static')) 59 | 60 | module.exports = app.listen(port, function (err) { 61 | if (err) { 62 | console.log(err) 63 | return 64 | } 65 | var uri = 'http://localhost:' + port 66 | console.log('Listening at ' + uri + '\n') 67 | opn(uri) 68 | }) 69 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var glob = require('glob') 4 | // 将样式提取到单独的css文件中,而不是打包到js文件或使用style标签插入在head标签中 5 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 6 | 7 | exports.assetsPath = function(_path) { 8 | var assetsSubDirectory = process.env.NODE_ENV === 'production' ? 9 | config.build.assetsSubDirectory : 10 | config.dev.assetsSubDirectory 11 | return path.posix.join(assetsSubDirectory, _path) 12 | } 13 | 14 | exports.cssLoaders = function(options) { 15 | options = options || {} 16 | // generate loader string to be used with extract text plugin 17 | function generateLoaders(loaders) { 18 | var sourceLoader = loaders.map(function(loader) { 19 | var extraParamChar 20 | if (/\?/.test(loader)) { 21 | loader = loader.replace(/\?/, '-loader?') 22 | extraParamChar = '&' 23 | } else { 24 | loader = loader + '-loader' 25 | extraParamChar = '?' 26 | } 27 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 28 | }).join('!') 29 | 30 | if (options.extract) { 31 | return ExtractTextPlugin.extract('vue-style-loader', sourceLoader) 32 | } else { 33 | return ['vue-style-loader', sourceLoader].join('!') 34 | } 35 | } 36 | 37 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 38 | return { 39 | css: generateLoaders(['css']), 40 | postcss: generateLoaders(['css']), 41 | less: generateLoaders(['css', 'less']), 42 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 43 | scss: generateLoaders(['css', 'sass']), 44 | stylus: generateLoaders(['css', 'stylus']), 45 | styl: generateLoaders(['css', 'stylus']) 46 | } 47 | } 48 | 49 | // Generate loaders for standalone style files (outside of .vue) 50 | exports.styleLoaders = function(options) { 51 | var output = [] 52 | var loaders = exports.cssLoaders(options) 53 | for (var extension in loaders) { 54 | var loader = loaders[extension] 55 | output.push({ 56 | test: new RegExp('\\.' + extension + '$'), 57 | loader: loader 58 | }) 59 | } 60 | return output 61 | } 62 | 63 | exports.getEntry = function(globPaths) { 64 | var entries = {}, 65 | basename, tmp, pathname; 66 | for (globPath of globPaths) { 67 | glob.sync(globPath).forEach(function(entry) { 68 | basename = path.basename(entry, path.extname(entry)); 69 | tmp = entry.split('/').splice(-3); 70 | pathname = tmp.splice(0, 1) + '/' + basename; // 正确输出js和html的路径 71 | entries[pathname] = entry; 72 | }); 73 | } 74 | console.log(entries); 75 | return entries; 76 | } -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var webpack = require('webpack') 4 | var merge = require('webpack-merge') 5 | var utils = require('./utils') 6 | var projectRoot = path.resolve(__dirname, '../') ///——driname当前目录 7 | var chunks = Object.keys(utils.getEntry(['./src/module/**/*.js', './src/m/**/*.js'])); 8 | // 将样式提取到单独的css文件中,而不是打包到js文件或使用style标签插入在head标签中 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 10 | module.exports = { 11 | entry: utils.getEntry(['./src/module/**/*.js', './src/m/**/*.js']), 12 | output: { 13 | path: config.build.assetsRoot, 14 | publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, //根名称可配置 15 | filename: '[name].js' 16 | }, 17 | resolve: { 18 | extensions: ['', '.js', '.vue'], 19 | fallback: [path.join(__dirname, '../node_modules')], 20 | alias: { 21 | 'src': path.resolve(__dirname, '../src'), 22 | 'assets': path.resolve(__dirname, '../src/assets'), 23 | 'components': path.resolve(__dirname, '../src/components'), 24 | 'jquery': 'jquery' 25 | } 26 | }, 27 | resolveLoader: { 28 | fallback: [path.join(__dirname, '../node_modules')] 29 | }, 30 | module: { 31 | loaders: [{ 32 | test: /\.vue$/, 33 | loader: 'vue-loader' 34 | }, 35 | { 36 | test: /\.js$/, 37 | loader: 'babel', 38 | include: projectRoot, 39 | exclude: /node_modules/ 40 | }, 41 | { 42 | test: /\.json$/, 43 | loader: 'json' 44 | }, 45 | { 46 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 47 | loader: 'url', 48 | query: { 49 | limit: 30000, 50 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 51 | } 52 | }, 53 | { 54 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 55 | loader: 'url', 56 | query: { 57 | limit: 10000, 58 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 59 | } 60 | } 61 | ] 62 | }, 63 | eslint: { 64 | formatter: require('eslint-friendly-formatter') 65 | }, 66 | vue: { 67 | loaders: utils.cssLoaders(), 68 | postcss: [ 69 | require('autoprefixer')({ 70 | browsers: ['last 2 versions'] 71 | }) 72 | ] 73 | }, 74 | plugins: [ 75 | // new webpack.optimize.CommonsChunkPlugin('static/build.js'), 76 | // 提取公共模块 77 | new webpack.optimize.CommonsChunkPlugin({ 78 | name: 'vendors', // 公共模块的名称 79 | chunks: chunks, // chunks是需要提取的模块 80 | minChunks: chunks.length 81 | }), 82 | // 配置提取出的样式文件 83 | new ExtractTextPlugin('css/[name].css'), 84 | new webpack.ProvidePlugin({ 85 | $: "jquery", 86 | jQuery: "jquery" 87 | }) 88 | ], 89 | } -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var config = require('../config') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var utils = require('./utils') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | // add hot-reload related code to entry chunks 8 | Object.keys(baseWebpackConfig.entry).forEach(function(name) { 9 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 10 | }) 11 | 12 | module.exports = merge(baseWebpackConfig, { 13 | module: { 14 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 15 | }, 16 | // eval-source-map is faster for development 17 | devtool: '#eval-source-map', 18 | plugins: [ 19 | new webpack.DefinePlugin({ 20 | 'process.env': config.dev.env 21 | }), 22 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 23 | new webpack.optimize.OccurenceOrderPlugin(), 24 | new webpack.HotModuleReplacementPlugin(), 25 | new webpack.NoErrorsPlugin(), 26 | // https://github.com/ampedandwired/html-webpack-plugin 27 | // new HtmlWebpackPlugin({ 28 | // filename: 'index.html', 29 | // template: 'index.html', 30 | // inject: true 31 | // }) 32 | ] 33 | }) 34 | 35 | var pages = utils.getEntry(['./src/module/**/*.html', './src/m/**/*.html']); 36 | 37 | 38 | for (var pathname in pages) { 39 | 40 | 41 | // 配置生成的html文件,定义路径等 42 | var conf = { 43 | filename: pathname + '.html', 44 | template: pages[pathname], // 模板路径 45 | favicon: './src/assets/images/wechat.png', 46 | inject: true // js插入位置 47 | 48 | }; 49 | 50 | 51 | if (pathname in module.exports.entry) { 52 | conf.chunks = ['vendors', pathname]; 53 | conf.hash = true; 54 | } 55 | 56 | module.exports.plugins.push(new HtmlWebpackPlugin(conf)); 57 | } -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var env = process.env.NODE_ENV === 'testing' ? 10 | require('../config/test.env') : 11 | config.build.env 12 | 13 | module.exports = merge(baseWebpackConfig, { 14 | module: { 15 | loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true }) 16 | }, 17 | devtool: config.build.productionSourceMap ? '#source-map' : false, 18 | output: { 19 | path: config.build.assetsRoot, 20 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 21 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 22 | }, 23 | vue: { 24 | loaders: utils.cssLoaders({ 25 | sourceMap: config.build.productionSourceMap, 26 | extract: true 27 | }) 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false, 37 | drop_debugger: true, 38 | drop_console: true 39 | } 40 | }), 41 | new webpack.optimize.OccurenceOrderPlugin(), 42 | // extract css into its own file 43 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 44 | // generate dist index.html with correct asset hash for caching. 45 | // you can customize output by editing /index.html 46 | // see https://github.com/ampedandwired/html-webpack-plugin 47 | // new HtmlWebpackPlugin({ 48 | // filename: process.env.NODE_ENV === 'testing' ? 49 | // 'index.html' : config.build.index, 50 | // template: 'index.html', 51 | // favicon: './src/assets/images/tjd.ico', 52 | // inject: true, 53 | // minify: { 54 | // removeComments: true, 55 | // collapseWhitespace: true, 56 | // removeAttributeQuotes: true 57 | // // more options: 58 | // // https://github.com/kangax/html-minifier#options-quick-reference 59 | // }, 60 | // // necessary to consistently work with multiple chunks via CommonsChunkPlugin 61 | // chunksSortMode: 'dependency' 62 | // }), 63 | // split vendor js into its own file 64 | new webpack.optimize.CommonsChunkPlugin({ 65 | name: 'vendor', 66 | minChunks: function(module, count) { 67 | // any required modules inside node_modules are extracted to vendor 68 | return ( 69 | module.resource && 70 | /\.js$/.test(module.resource) && 71 | module.resource.indexOf( 72 | path.join(__dirname, '../node_modules') 73 | ) === 0 74 | ) 75 | } 76 | }), 77 | // extract webpack runtime and module manifest to its own file in order to 78 | // prevent vendor hash from being updated whenever app bundle is updated 79 | new webpack.optimize.CommonsChunkPlugin({ 80 | name: 'manifest', 81 | chunks: ['vendor'] 82 | }) 83 | ] 84 | }) 85 | 86 | if (config.build.productionGzip) { 87 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 88 | 89 | webpackConfig.plugins.push( 90 | new CompressionWebpackPlugin({ 91 | asset: '[path].gz[query]', 92 | algorithm: 'gzip', 93 | test: new RegExp( 94 | '\\.(' + 95 | config.build.productionGzipExtensions.join('|') + 96 | ')$' 97 | ), 98 | threshold: 10240, 99 | minRatio: 0.8 100 | }) 101 | ) 102 | } 103 | 104 | var pages = utils.getEntry(['./src/module/**/*.html', './src/m/**/*.html']); 105 | 106 | for (var pathname in pages) { 107 | 108 | 109 | // 配置生成的html文件,定义路径等 110 | var conf = { 111 | filename: pathname + '.html', 112 | template: pages[pathname], // 模板路径 113 | favicon: './src/assets/images/wechat.png', 114 | inject: true // js插入位置 115 | 116 | }; 117 | if (pathname in pages) { 118 | conf.chunks = ['vendors', pathname]; 119 | conf.hash = true; 120 | } 121 | 122 | module.exports.plugins.push(new HtmlWebpackPlugin(conf)); 123 | } -------------------------------------------------------------------------------- /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: '/newmerchant/', 11 | productionSourceMap: false, //是否打开打包映射文件 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 | }, 19 | dev: { 20 | env: require('./dev.env'), 21 | port: 8080, 22 | assetsSubDirectory: 'static', 23 | assetsPublicPath: '/', 24 | proxyTable: {}, 25 | // CSS Sourcemaps off by default because relative paths are "buggy" 26 | // with this option, according to the CSS-Loader README 27 | // (https://github.com/webpack/css-loader#sourcemaps) 28 | // In our experience, they generally work as expected, 29 | // just be aware of this issue when enabling this option. 30 | cssSourceMap: false 31 | } 32 | } -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp')//加载gulp模块 4 | var webpack = require('webpack') 5 | var Q = require('q')//Q模块。适用于把异步转成同步执行,抽空换成ES6实现 6 | var minimist = require('minimist')//传参 7 | var ora = require('ora')//终端管理 8 | var pro = require('child_process')//运行子进程库 9 | var sftp = require('gulp-sftp')//sftp上传程序 10 | var knownOptions = { 11 | string: 'env', 12 | default: {env: process.env.NODE_ENV || 'production'} 13 | }; 14 | 15 | var gutil = require('gulp-util'), 16 | options = minimist(process.argv.slice(2), knownOptions), 17 | src = process.cwd() + '/src', 18 | assets = process.cwd() + '/dist', 19 | remoteServer1 = { 20 | host: '123.56.7.118', 21 | port:22444, 22 | remotePath: '/server/nginx/html/newmerchant/newmerchant', 23 | user: 'tjd', 24 | pass: 'tjd.ser.lb1' 25 | }, 26 | remoteServer2 = { 27 | host: '123.56.8.169', 28 | port:22444, 29 | remotePath: '/server/nginx/html/newmerchant/newmerchant', 30 | user: 'tjd', 31 | pass: 'tjd.ser.lb2' 32 | } 33 | //先执行build命令打包。在把程序上传至服务器 34 | gulp.task('deploy',() => build().then(() => { 35 | gulp.src(assets + '/**').pipe(sftp(remoteServer1)) 36 | gulp.src(assets + '/**').pipe(sftp(remoteServer2)) 37 | }) 38 | ) 39 | var build = () => { 40 | var deferred = Q.defer() 41 | pro.exec('npm run build',(error, stdout, stderr) => { 42 | if (error !== null) { 43 | console.log('exec error: ' + error) 44 | } 45 | console.log('build codeing finished') 46 | deferred.resolve() 47 | }) 48 | return deferred.promise 49 | } 50 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 停简单电子优惠系统 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "merchant-web", 3 | "version": "1.0.0", 4 | "description": "商家扫码", 5 | "author": "tjd", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "build": "node build/build.js", 10 | "deploy": "gulp deploy --env debug", 11 | "test": "npm run unit", 12 | "lint": "eslint --ext .js,.vue src test/unit/specs" 13 | }, 14 | "dependencies": { 15 | "glob": "^7.1.1", 16 | "jquery": "^2.2.4", 17 | "vue": "^2.0.1", 18 | "vue-loader": "^9.9.5", 19 | "vue-resource": "^1.0.3", 20 | "vue-router": "^2.0.0", 21 | "vuex": "^2.0.0" 22 | }, 23 | "devDependencies": { 24 | "autoprefixer": "^6.4.0", 25 | "babel-core": "^6.0.0", 26 | "babel-eslint": "^7.0.0", 27 | "babel-loader": "^6.0.0", 28 | "babel-plugin-transform-runtime": "^6.0.0", 29 | "babel-preset-es2015": "^6.0.0", 30 | "babel-preset-stage-2": "^6.0.0", 31 | "babel-register": "^6.0.0", 32 | "connect-history-api-fallback": "^1.1.0", 33 | "css-loader": "^0.25.0", 34 | "eslint": "^3.7.1", 35 | "eslint-friendly-formatter": "^2.0.5", 36 | "eslint-loader": "^1.5.0", 37 | "eslint-plugin-html": "^1.3.0", 38 | "eslint-config-standard": "^6.1.0", 39 | "eslint-plugin-promise": "^2.0.1", 40 | "eslint-plugin-standard": "^2.0.1", 41 | "eventsource-polyfill": "^0.9.6", 42 | "express": "^4.13.3", 43 | "extract-text-webpack-plugin": "^1.0.1", 44 | "file-loader": "^0.9.0", 45 | "function-bind": "^1.0.2", 46 | "html-webpack-plugin": "^2.8.1", 47 | "http-proxy-middleware": "^0.17.2", 48 | "json-loader": "^0.5.4", 49 | "lolex": "^1.4.0", 50 | "mocha": "^3.1.0", 51 | "chai": "^3.5.0", 52 | "sinon": "^1.17.3", 53 | "sinon-chai": "^2.8.0", 54 | "inject-loader": "^2.0.1", 55 | "isparta-loader": "^2.0.0", 56 | "phantomjs-prebuilt": "^2.1.3", 57 | "opn": "^4.0.2", 58 | "ora": "^0.3.0", 59 | "shelljs": "^0.7.4", 60 | "url-loader": "^0.5.7", 61 | "vue-style-loader": "^1.0.0", 62 | "webpack": "^1.13.2", 63 | "webpack-dev-middleware": "^1.8.3", 64 | "webpack-hot-middleware": "^2.12.2", 65 | "webpack-merge": "^0.8.3", 66 | "webpack-core": "^0.6.9", 67 | "gulp": "^3.9.1", 68 | "gulp-clean": "^0.3.2", 69 | "gulp-jshint": "^2.0.1", 70 | "gulp-sftp": "^0.1.5", 71 | "gulp-util": "^3.0.7", 72 | "minimist": "^1.2.0", 73 | "q": "^1.0.1" 74 | } 75 | } -------------------------------------------------------------------------------- /src/assets/css/common.css: -------------------------------------------------------------------------------- 1 | 2 | @charset "utf-8"; 3 | 4 | /*全局*/ 5 | 6 | body, 7 | div, 8 | p, 9 | ul, 10 | ol, 11 | li, 12 | dl, 13 | dt, 14 | dd, 15 | form, 16 | input, 17 | table, 18 | img, 19 | h1, 20 | h2, 21 | h3, 22 | h4, 23 | h5, 24 | h6, 25 | header, 26 | section, 27 | footer, 28 | nav, 29 | figure, 30 | figcaption, 31 | article { 32 | margin: 0; 33 | padding: 0 34 | } 35 | 36 | header, 37 | section, 38 | footer, 39 | nav, 40 | figure, 41 | figcaption, 42 | article, 43 | menu { 44 | display: block 45 | } 46 | 47 | body { 48 | font-family: 'Microsoft YaHei', 'Heiti SC', simhei, 'Lucida Sans Unicode', 'Myriad Pro', 'Hiragino Sans GB', Verdana; 49 | } 50 | 51 | ul { 52 | list-style: none; 53 | } 54 | 55 | a, 56 | a:hover { 57 | text-decoration: none; 58 | color: #393737; 59 | } 60 | 61 | img { 62 | border: none; 63 | display: inline-block; 64 | vertical-align: top; 65 | } 66 | 67 | h1, 68 | h2, 69 | h3, 70 | h4, 71 | h5, 72 | h6 { 73 | font-size: 16px; 74 | } 75 | 76 | em, 77 | i { 78 | font-style: normal; 79 | } 80 | 81 | .l, 82 | .left { 83 | float: left; 84 | display: inline-block; 85 | } 86 | 87 | .r, 88 | .right { 89 | float: right; 90 | display: inline-block; 91 | } 92 | 93 | .clear:after { 94 | content: ''; 95 | display: block; 96 | width: 0; 97 | height: 0; 98 | overflow: hidden; 99 | clear: both; 100 | } 101 | 102 | header, 103 | nav, 104 | section, 105 | footer { 106 | min-width: 320px; 107 | } 108 | 109 | html, 110 | body, 111 | div, 112 | span, 113 | applet, 114 | object, 115 | iframe, 116 | h1, 117 | h2, 118 | h3, 119 | h4, 120 | h5, 121 | h6, 122 | p, 123 | blockquote, 124 | pre, 125 | a, 126 | abbr, 127 | acronym, 128 | address, 129 | big, 130 | cite, 131 | code, 132 | del, 133 | dfn, 134 | em, 135 | font, 136 | img, 137 | ins, 138 | kbd, 139 | q, 140 | s, 141 | samp, 142 | small, 143 | strike, 144 | strong, 145 | sub, 146 | sup, 147 | tt, 148 | var, 149 | dd, 150 | dl, 151 | dt, 152 | li, 153 | ol, 154 | ul, 155 | fieldset, 156 | form, 157 | label, 158 | legend, 159 | table, 160 | caption, 161 | tbody, 162 | tfoot, 163 | thead, 164 | tr, 165 | th, 166 | td { 167 | margin: 0; 168 | padding: 0; 169 | border: 0; 170 | } 171 | 172 | table { 173 | border-collapse: collapse; 174 | border-spacing: 0; 175 | } 176 | 177 | ol, 178 | ul { 179 | list-style: none; 180 | } 181 | select{ 182 | appearance: none; 183 | -moz-appearance: none; 184 | -webkit-appearance: none; 185 | } 186 | q:before, 187 | q:after, 188 | blockquote:before, 189 | blockquote:after { 190 | content: ""; 191 | } 192 | 193 | input[type="button"], 194 | input[type="submit"], 195 | input[type="type"], 196 | input[type="reset"] { 197 | -webkit-appearance: none !important; 198 | -o-appearance: none !important; 199 | -moz-appearance: none !important; 200 | -ms-appearance: none !important; 201 | } 202 | 203 | input::-webkit-outer-spin-button, 204 | input::-webkit-inner-spin-button { 205 | -webkit-appearance: none !important; 206 | -o-appearance: none !important; 207 | -moz-appearance: none !important; 208 | -ms-appearance: none !important; 209 | } 210 | 211 | a, 212 | img, 213 | button, 214 | input, 215 | textarea { 216 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 217 | } 218 | 219 | a, 220 | img, 221 | button, 222 | input, 223 | textarea { 224 | -webkit-appearance: none; 225 | } 226 | 227 | body, 228 | html { 229 | margin: 0; 230 | padding: 0; 231 | height: 100%; 232 | width: 100%; 233 | background: #e9edee; 234 | font-family: 'Microsoft YaHei', 'Heiti SC', simhei, 'Lucida Sans Unicode', 'Myriad Pro', 'Hiragino Sans GB', Verdana; 235 | font-size: 16px; 236 | -webkit-user-select: none; 237 | } 238 | .fade-enter-active,.fade-leave-active{ 239 | transition: opacity .3s; 240 | } 241 | .fade-enter,.fade-leave-active{ 242 | opacity: 0; 243 | } 244 | @media screen and (min-width: 960px) and (max-width: 1024px) { 245 | body,html{ 246 | margin: 0; 247 | padding: 0; 248 | height: 100%; 249 | width:100%; 250 | background: #e9edee; 251 | font-family: 'Microsoft YaHei','Heiti SC',simhei,'Lucida Sans Unicode','Myriad Pro','Hiragino Sans GB',Verdana; 252 | font-size: 16px; 253 | -webkit-user-select:none; 254 | } 255 | } -------------------------------------------------------------------------------- /src/assets/css/m.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /*全局*/ 4 | 5 | body, 6 | div, 7 | p, 8 | ul, 9 | ol, 10 | li, 11 | dl, 12 | dt, 13 | dd, 14 | form, 15 | input, 16 | table, 17 | img, 18 | h1, 19 | h2, 20 | h3, 21 | h4, 22 | h5, 23 | h6, 24 | header, 25 | section, 26 | footer, 27 | nav, 28 | figure, 29 | figcaption, 30 | article { 31 | margin: 0; 32 | padding: 0 33 | } 34 | 35 | header, 36 | section, 37 | footer, 38 | nav, 39 | figure, 40 | figcaption, 41 | article, 42 | menu { 43 | display: block 44 | } 45 | 46 | body { 47 | font-family: 'Microsoft YaHei', 'Heiti SC', simhei, 'Lucida Sans Unicode', 'Myriad Pro', 'Hiragino Sans GB', Verdana; 48 | } 49 | 50 | ul { 51 | list-style: none; 52 | } 53 | 54 | a, 55 | a:hover { 56 | text-decoration: none; 57 | color: #393737; 58 | } 59 | 60 | img { 61 | border: none; 62 | display: inline-block; 63 | vertical-align: top; 64 | } 65 | 66 | h1, 67 | h2, 68 | h3, 69 | h4, 70 | h5, 71 | h6 { 72 | font-size: 16px; 73 | } 74 | 75 | em, 76 | i { 77 | font-style: normal; 78 | } 79 | 80 | .l, 81 | .left { 82 | float: left; 83 | display: inline-block; 84 | } 85 | 86 | .r, 87 | .right { 88 | float: right; 89 | display: inline-block; 90 | } 91 | 92 | .clear:after { 93 | content: ''; 94 | display: block; 95 | width: 0; 96 | height: 0; 97 | overflow: hidden; 98 | clear: both; 99 | } 100 | 101 | header, 102 | nav, 103 | section, 104 | footer { 105 | min-width: 320px; 106 | } 107 | 108 | html, 109 | body, 110 | div, 111 | span, 112 | applet, 113 | object, 114 | iframe, 115 | h1, 116 | h2, 117 | h3, 118 | h4, 119 | h5, 120 | h6, 121 | p, 122 | blockquote, 123 | pre, 124 | a, 125 | abbr, 126 | acronym, 127 | address, 128 | big, 129 | cite, 130 | code, 131 | del, 132 | dfn, 133 | em, 134 | font, 135 | img, 136 | ins, 137 | kbd, 138 | q, 139 | s, 140 | samp, 141 | small, 142 | strike, 143 | strong, 144 | sub, 145 | sup, 146 | tt, 147 | var, 148 | dd, 149 | dl, 150 | dt, 151 | li, 152 | ol, 153 | ul, 154 | fieldset, 155 | form, 156 | label, 157 | legend, 158 | table, 159 | caption, 160 | tbody, 161 | tfoot, 162 | thead, 163 | tr, 164 | th, 165 | td { 166 | margin: 0; 167 | padding: 0; 168 | border: 0; 169 | } 170 | 171 | table { 172 | border-collapse: collapse; 173 | border-spacing: 0; 174 | } 175 | 176 | ol, 177 | ul { 178 | list-style: none; 179 | } 180 | 181 | q:before, 182 | q:after, 183 | blockquote:before, 184 | blockquote:after { 185 | content: ""; 186 | } 187 | 188 | input[type="button"], 189 | input[type="submit"], 190 | input[type="type"], 191 | input[type="reset"] { 192 | -webkit-appearance: none !important; 193 | -o-appearance: none !important; 194 | -moz-appearance: none !important; 195 | -ms-appearance: none !important; 196 | } 197 | 198 | input::-webkit-outer-spin-button, 199 | input::-webkit-inner-spin-button { 200 | -webkit-appearance: none !important; 201 | -o-appearance: none !important; 202 | -moz-appearance: none !important; 203 | -ms-appearance: none !important; 204 | } 205 | 206 | a, 207 | img, 208 | button, 209 | input, 210 | textarea { 211 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 212 | } 213 | 214 | a, 215 | img, 216 | button, 217 | input, 218 | textarea { 219 | -webkit-appearance: none; 220 | } 221 | 222 | #wrapper { 223 | position: absolute; 224 | top: 44px; 225 | width: 100%; 226 | } 227 | 228 | .alertcontent { 229 | width: 90%; 230 | height: auto; 231 | margin: 0 auto; 232 | } 233 | 234 | .alertnotice_sure { 235 | width: 90%; 236 | height: auto; 237 | text-align: center; 238 | position: fixed; 239 | top: 20%; 240 | z-index: 999; 241 | border-radius: 10px; 242 | background: #ffffff; 243 | } 244 | 245 | .alertcontent p { 246 | text-align: center; 247 | line-height: 24px; 248 | color: #3293fa; 249 | padding-top: 10px; 250 | font-size: 18px; 251 | } 252 | 253 | .alertcontent h1 { 254 | color: #787878; 255 | font-weight: normal; 256 | text-align: center; 257 | padding-bottom: 11px; 258 | font-size: 15px; 259 | } 260 | 261 | .close { 262 | position: absolute; 263 | right: 10px; 264 | top: 10px; 265 | width: 1em; 266 | } 267 | 268 | .zhezhao { 269 | width: 100%; 270 | height: 100%; 271 | background: rgba(0, 0, 0, 0.3); 272 | position: fixed; 273 | left: 0; 274 | top: 0; 275 | z-index: 9999; 276 | } 277 | 278 | .fullbg { 279 | background-color: #000000; 280 | left: 0; 281 | opacity: 0.6; 282 | position: fixed; 283 | top: 0; 284 | width: 100%; 285 | height: 100%!important; 286 | z-index: 999; 287 | } -------------------------------------------------------------------------------- /src/assets/images/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawnyu/vue-cli-multipage/fd279916e977537e33f48ba517e3b6636d254f27/src/assets/images/wechat.png -------------------------------------------------------------------------------- /src/assets/util.js: -------------------------------------------------------------------------------- 1 | /**获取静态页名称 */ 2 | export const getUrlName = () => { 3 | let url = window.location.href 4 | if (!url) return 5 | return url.slice(url.lastIndexOf("/") + 1, url.lastIndexOf(".html")) 6 | } -------------------------------------------------------------------------------- /src/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 107 | 721 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | //测试地址 2 | export const testApiPath = 'http://www.baidu.com'; 3 | //生产地址,上线时需要把链接修改生产,测试情况下测试地址和生产地址都是测试地址 4 | export const productApiPath = 'www.baidu.com'; 5 | //项目名称 6 | export const rootName = 'vue-mult'; -------------------------------------------------------------------------------- /src/filters/dateFormatter.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 个位数前加零 4 | * @param {Number} val 5 | * @return {String/Number} 6 | */ 7 | let zerofill = val => val >= 10 ? val : '0' + val 8 | 9 | const dateFormatter = (time,timeType, type) => { 10 | if(!time) return 11 | if(timeType == 1){ 12 | time = time.slice(0,4).concat('/').concat(time.slice(4,6)).concat('/').concat(time.slice(6,8)) 13 | .concat(' ').concat(time.slice(8,10)).concat(':').concat(time.slice(10,12)).concat(':').concat(time.slice(12,14)) 14 | } 15 | let date = new Date(time) 16 | let year = date.getFullYear() 17 | let month = date.getMonth() + 1 18 | let day = date.getDate() 19 | let hours = date.getHours() 20 | let minutes = date.getMinutes() 21 | let second = date.getSeconds() 22 | 23 | switch (type) { 24 | case 0: // 01-05 25 | return `${zerofill(month)}-${zerofill(day)}` 26 | case 1: // 11:12 27 | return `${zerofill(hours)}-${zerofill(minutes)}` 28 | case 2: // 2016-11-05 29 | return `${year}-${zerofill(month)}-${zerofill(day)}` 30 | case 3: // 2016-11-05 11:12 31 | return `${year}-${zerofill(month)}-${zerofill(day)} ${zerofill(hours)}:${zerofill(minutes)}` 32 | case 4: // 2016.11.05 11:12 33 | return `${year}.${zerofill(month)}.${zerofill(day)} ${zerofill(hours)}:${zerofill(minutes)}:${zerofill(second)}` 34 | case 5: // 2016-11-05 35 | return `${year}.${zerofill(month)}.${zerofill(day)}` 36 | case 6: // 2016-11-05 37 | return `${hours}:${zerofill(minutes)}:${zerofill(second)}` 38 | case 7: // 2016-11-05 11:12:13 39 | return `${year}-${zerofill(month)}-${zerofill(day)} ${zerofill(hours)}:${zerofill(minutes)}:${zerofill(second)}` 40 | case 8: // 11:12:13 41 | return `${zerofill(hours)}:${zerofill(minutes)}:${zerofill(second)}` 42 | default: 43 | return "" 44 | } 45 | } 46 | 47 | const parseParkingTime = (time) =>{ 48 | if (!time) { 49 | return '00:00:00'; 50 | } 51 | time = time * 1; 52 | let h = parseInt((time / 60) / 60); 53 | let m = parseInt((time - h * 60 * 60) / 60); 54 | let s = (time - h * 60 * 60 - m * 60) % 60; 55 | if ((h + '').length < 2) { 56 | h = '0' + h; 57 | } 58 | if ((s + '').length < 2) { 59 | s = '0' + s; 60 | } 61 | if ((m + '').length < 2) { 62 | m = '0' + m; 63 | } 64 | return h + ':' + m + ':' + s; 65 | } 66 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 此处须把所有过滤器全局化 3 | */ 4 | import Vue from 'vue' 5 | Vue.filter('dateFormatter', require('./dateFormatter').dateFormatter) -------------------------------------------------------------------------------- /src/m/login/app.vue: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 101 | 102 | -------------------------------------------------------------------------------- /src/m/login/login.css: -------------------------------------------------------------------------------- 1 | @import '../../assets/css/m.css'; 2 | #wrapper { 3 | margin-top: 35px; 4 | } 5 | 6 | .content p { 7 | text-align: center; 8 | } 9 | 10 | .content p input { 11 | height: 40px; 12 | border: none; 13 | width: 88%; 14 | border-radius: 5px; 15 | margin-bottom: 20px; 16 | outline: none; 17 | font-family: "Microsoft YaHei"; 18 | padding-left: 2%; 19 | font-size: 16px; 20 | background: #fff; 21 | } 22 | 23 | #sure { 24 | background: #CCCCCC; 25 | color: #fff; 26 | } 27 | 28 | .submit-sure { 29 | color: #fff!important; 30 | background: #2789e2!important; 31 | } 32 | 33 | .dels { 34 | display: none; 35 | position: absolute; 36 | right: 6%; 37 | padding-top: 10px; 38 | } 39 | 40 | .dels img { 41 | width: 62%; 42 | } 43 | 44 | header { 45 | position: fixed; 46 | width: 100%; 47 | height: 44px; 48 | background: #2889e3; 49 | line-height: 44px; 50 | text-align: center; 51 | font-size: 17px; 52 | color: #ffffff; 53 | z-index: 999; 54 | } 55 | 56 | .icon-input-clear { 57 | position: absolute; 58 | right: 43px; 59 | height: 40px; 60 | top: 11%; 61 | -webkit-transform: translate(0, -50%); 62 | -ms-transform: translate(0, -50%); 63 | transform: translate(0, -50%); 64 | } 65 | 66 | .icon-input-clear::before { 67 | content: ""; 68 | position: absolute; 69 | top: 50%; 70 | left: 50%; 71 | width: 26px; 72 | height: 26px; 73 | -webkit-transform: translate(-50%, -50%); 74 | -ms-transform: translate(-50%, -50%); 75 | transform: translate(-50%, -50%); 76 | border-radius: 15px; 77 | background-color: #ccc; 78 | color: #fff; 79 | } 80 | 81 | .icon-input-clear .icon-cross { 82 | position: absolute; 83 | top: 50%; 84 | left: 50%; 85 | color: #fff; 86 | } 87 | 88 | .icon-cross::before, 89 | .icon-cross::after { 90 | content: ""; 91 | position: absolute; 92 | left: 50%; 93 | top: 50%; 94 | width: 2px; 95 | height: 14px; 96 | -webkit-transform: translate(-50%, -50%) rotate(45deg); 97 | -ms-transform: translate(-50%, -50%) rotate(45deg); 98 | transform: translate(-50%, -50%) rotate(45deg); 99 | background-color: currentColor; 100 | } 101 | 102 | .icon-cross::after { 103 | -webkit-transform: translate(-50%, -50%) rotate(-45deg); 104 | -ms-transform: translate(-50%, -50%) rotate(-45deg); 105 | transform: translate(-50%, -50%) rotate(-45deg); 106 | } -------------------------------------------------------------------------------- /src/m/login/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | vue-cli改造多页面 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/m/login/login.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './app' 3 | const app = new Vue({ 4 | el: '#app', 5 | render: h => h(App) 6 | }) -------------------------------------------------------------------------------- /src/module/login/app.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 119 | 120 | -------------------------------------------------------------------------------- /src/module/login/login.css: -------------------------------------------------------------------------------- 1 | @import '../../assets/css/common.css'; 2 | .login { 3 | width: 570px; 4 | height: 665px; 5 | background: #ffffff; 6 | box-shadow: 2px 2px 2px #e0e0e0; 7 | margin: 0 auto; 8 | margin-top: 8%; 9 | position: relative; 10 | } 11 | 12 | .login p {text-align: center;} 13 | 14 | .login_logo { 15 | width: 100%; 16 | position: absolute; 17 | top: -71px; 18 | } 19 | 20 | .login_logo img { 21 | border: 10px solid #e9edee; 22 | border-radius: 75px; 23 | } 24 | 25 | .title { 26 | line-height: 190px; 27 | color: #2085ef; 28 | font-size: 35px; 29 | padding-top: 65px; 30 | } 31 | 32 | .login p>input { 33 | height: 70px; 34 | border: none; 35 | width: 420px; 36 | border-radius: 10px; 37 | margin-bottom: 20px; 38 | outline: none; 39 | font-family: "Microsoft YaHei"; 40 | padding-left: 30px; 41 | font-size: 20px; 42 | background: #e9edee; 43 | } 44 | 45 | .login_sure>input { 46 | width: 450px!important; 47 | text-align: center; 48 | margin: 0 auto; 49 | padding: 0!important; 50 | color: #BBB8B8; 51 | } 52 | 53 | .login p:nth-child(2) { 54 | padding-top: 240px; 55 | } 56 | 57 | .chose { 58 | width: 86%; 59 | margin-top: 15px; 60 | margin-bottom: 50px; 61 | text-align: right!important 62 | } 63 | 64 | .chose b { 65 | margin-left: 5px; 66 | display: inline-block; 67 | float: right; 68 | line-height: 17px; 69 | font-weight: normal; 70 | } 71 | 72 | header { 73 | text-align: center; 74 | font-size: 2.5em; 75 | color: #2085ef; 76 | margin-top: 5%; 77 | } 78 | 79 | .login p>label { 80 | margin-right: 2%; 81 | } 82 | 83 | .submit-sure { 84 | color: #fff!important; 85 | background: #2789e2!important; 86 | } 87 | 88 | .login p:nth-child(3)>label { 89 | margin-right: 4%; 90 | } 91 | 92 | .icon-input-clear { 93 | position: absolute; 94 | right: 60px; 95 | height: 40px; 96 | top: 41%; 97 | -webkit-transform: translate(0, -50%); 98 | -ms-transform: translate(0, -50%); 99 | transform: translate(0, -50%); 100 | } 101 | 102 | .icon-input-clear::before { 103 | content: ""; 104 | position: absolute; 105 | top: 50%; 106 | left: 50%; 107 | width: 26px; 108 | height: 26px; 109 | -webkit-transform: translate(-50%, -50%); 110 | -ms-transform: translate(-50%, -50%); 111 | transform: translate(-50%, -50%); 112 | border-radius: 15px; 113 | background-color: #ccc; 114 | color: #fff; 115 | } 116 | 117 | .icon-input-clear .icon-cross { 118 | position: absolute; 119 | top: 50%; 120 | left: 50%; 121 | color: #fff; 122 | } 123 | 124 | .icon-cross::before, 125 | .icon-cross::after { 126 | content: ""; 127 | position: absolute; 128 | left: 50%; 129 | top: 50%; 130 | width: 2px; 131 | height: 14px; 132 | -webkit-transform: translate(-50%, -50%) rotate(45deg); 133 | -ms-transform: translate(-50%, -50%) rotate(45deg); 134 | transform: translate(-50%, -50%) rotate(45deg); 135 | background-color: currentColor; 136 | } 137 | 138 | .icon-cross::after { 139 | -webkit-transform: translate(-50%, -50%) rotate(-45deg); 140 | -ms-transform: translate(-50%, -50%) rotate(-45deg); 141 | transform: translate(-50%, -50%) rotate(-45deg); 142 | } 143 | 144 | 145 | /*适配ipad**/ 146 | 147 | @media screen and (min-width: 960px) and (max-width: 1024px) { 148 | body { 149 | overflow: hidden; 150 | } 151 | .login { 152 | width: 50%; 153 | height: 400px; 154 | margin-top: 15%; 155 | } 156 | .login p:nth-child(2) { 157 | padding-top: 122px; 158 | } 159 | .login p>label { 160 | margin-right: 2%; 161 | } 162 | .title { 163 | line-height: 145px; 164 | font-size: 24px; 165 | padding-top: 50px; 166 | } 167 | .title { 168 | line-height: 145px; 169 | color: #2085ef; 170 | font-size: 24px; 171 | padding-top: 50px; 172 | } 173 | .login p>input { 174 | height: 50px; 175 | border: none; 176 | width: 60%; 177 | border-radius: 5px; 178 | margin-bottom: 10px; 179 | outline: none; 180 | font-family: "Microsoft YaHei"; 181 | padding-left: 3%; 182 | font-size: 16px; 183 | background: #e9edee; 184 | } 185 | .login_sure>input { 186 | width: 70%!important; 187 | } 188 | .submit-sure { 189 | color: #fff!important; 190 | background: #2789e2!important; 191 | } 192 | .chose { 193 | -moz-user-select: none; 194 | -webkit-user-select: none; 195 | -ms-user-select: none; 196 | -khtml-user-select: none; 197 | user-select: none; 198 | } 199 | .chose b { 200 | margin-left: 10px; 201 | } 202 | .icon-input-clear { 203 | right: 85px; 204 | top: 37%; 205 | } 206 | } -------------------------------------------------------------------------------- /src/module/login/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 停简单电子优惠系统 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/module/login/login.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './app' 3 | const app = new Vue({ 4 | el: '#app', 5 | render: h => h(App) 6 | }) -------------------------------------------------------------------------------- /src/services/service.js: -------------------------------------------------------------------------------- 1 | import xhr from './xhr/' 2 | /** 3 | * 对应后端的 /api/* 所有 API 4 | */ 5 | class Service{ 6 | //登录 7 | login(params,_this){ 8 | this.isLoading() 9 | return xhr(params,_this,'post','/center/login') 10 | } 11 | } 12 | 13 | export default new MerchantService() 14 | -------------------------------------------------------------------------------- /src/services/xhr/config.js: -------------------------------------------------------------------------------- 1 | import {productApiPath,testApiPath} from '../../config' 2 | 3 | export const local = false//本地调试时开启,生产环境关闭 4 | export const rootPath = process.env.NODE_ENV == 'production'?productApiPath:testApiPath 5 | -------------------------------------------------------------------------------- /src/services/xhr/index.js: -------------------------------------------------------------------------------- 1 | import xhr from './vueResource' 2 | 3 | 4 | /** 5 | * XHR 请求接口定义 6 | * @param {String} options.method 请求方法,默认为 get。支持 post、put、patch、delete 等 7 | * @param {String} options.url 请求路径,基于 rootPath 地址。例:欲请求 http://localhost:9000/user,仅需要填写 /user 即可 8 | * @param {Object} options.body 请求体。后端 Express 使用 req.body 获取该对象 9 | * @return {Promise} 10 | * 11 | * 使用例子 xhr({ method: 'post', url: 'XXX', body: {Object} }) 12 | * 最简单的例子 xhr({ url: '/user' }) 13 | */ 14 | export default xhr 15 | -------------------------------------------------------------------------------- /src/services/xhr/vueResource.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueResource from 'vue-resource' 3 | import { rootPath,local } from './config' 4 | import {rootName} from '../../config' 5 | 6 | Vue.use(VueResource) 7 | Vue.http.options.root = rootPath 8 | Vue.http.options.emulateJSON = true 9 | Vue.http.options.xhr = { withCredentials: true } 10 | const xhr = ( body, method = 'get',url) => { 11 | // Object.assign(body, {token:sessionStorage.getItem("tjd_token")}); 12 | // 引入了 ES6 的 Promise 实现 13 | return new Promise((resolve, reject) => { 14 | Vue.http[method.toLowerCase()](rootPath + url, body) 15 | .then(({ data }) => { 16 | if (!data) 17 | return resolve(null) 18 | if(data.isSuccess == 0){ 19 | resolve(data) 20 | }else if (data.isSuccess == 2){ 21 | //登录超时,重新登录 22 | return errHandler(data.errorMSG,()=>{ 23 | window.location.href='/'+rootName+'/login.html' 24 | }) 25 | }else{ 26 | //如果传入this对象,则统一拦截错误信息。否则返回错误 27 | resolve(data) 28 | } 29 | 30 | }, errHandler) 31 | }) 32 | } 33 | const errHandler = (e,callback) => { 34 | if(e.ok){ 35 | alert("alert","",'网络异常,请检查网络',callback) 36 | return 37 | } 38 | if(!e.ok && e.status == 0){ 39 | alert("alert","",'网络异常,请检查网络!') 40 | return 41 | } 42 | if(!e.ok && e.status == 400){ 43 | alert("alert","",e.body.message) 44 | return 45 | } 46 | if(!e.ok && e.status == 404){ 47 | alert("alert","",'接口地址不存在!') 48 | return 49 | } 50 | if(!e.ok && e.status == 405){ 51 | alert("alert","",'接口地址有误!') 52 | return 53 | } 54 | if(!e.ok && e.status == 500){ 55 | alert("alert","",'服务器通讯异常!') 56 | return 57 | } 58 | if(e =='登陆超时,请重新登陆'){ 59 | callback() 60 | }else{ 61 | alert("alert","",e) 62 | return 63 | } 64 | 65 | } 66 | 67 | export default xhr 68 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawnyu/vue-cli-multipage/fd279916e977537e33f48ba517e3b6636d254f27/static/.gitkeep --------------------------------------------------------------------------------