| @@ -0,0 +1,13 @@ | |||
| { | |||
| "presets": [ | |||
| ["env", { | |||
| "modules": false, | |||
| "targets": { | |||
| "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] | |||
| } | |||
| }], | |||
| "stage-2" | |||
| ], | |||
| "plugins": ["transform-vue-jsx", "transform-runtime"] | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| root = true | |||
| [*] | |||
| charset = utf-8 | |||
| indent_style = space | |||
| indent_size = 2 | |||
| end_of_line = lf | |||
| insert_final_newline = true | |||
| trim_trailing_whitespace = true | |||
| @@ -0,0 +1,14 @@ | |||
| .DS_Store | |||
| node_modules/ | |||
| /dist/ | |||
| npm-debug.log* | |||
| yarn-debug.log* | |||
| yarn-error.log* | |||
| # Editor directories and files | |||
| .idea | |||
| .vscode | |||
| *.suo | |||
| *.ntvs* | |||
| *.njsproj | |||
| *.sln | |||
| @@ -0,0 +1,22 @@ | |||
| const AutoPrefixer = require("autoprefixer"); | |||
| const px2rem = require("postcss-px2rem"); | |||
| module.exports = ({ file }) => { | |||
| let remUnit; | |||
| // 判断条件 请自行调整 我使用的是 mand-mobile ui 没有对vant引入进行测试 | |||
| //link https://github.com/youzan/vant/issues/1181 | |||
| if (file && file.dirname && file.dirname.indexOf("vant") > -1) { | |||
| remUnit = 37.5; | |||
| }else { | |||
| remUnit = 75; | |||
| } | |||
| return { | |||
| plugins: [ | |||
| px2rem({ remUnit: remUnit}), | |||
| AutoPrefixer({ browsers: ["last 20 versions", "android >= 4.0"] }) | |||
| ] | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,21 @@ | |||
| # nsgk_frame | |||
| > | |||
| ## Build Setup | |||
| ``` bash | |||
| # install dependencies | |||
| npm install | |||
| # serve with hot reload at localhost:8080 | |||
| npm run dev | |||
| # build for production with minification | |||
| npm run build | |||
| # build for production and view the bundle analyzer report | |||
| npm run build --report | |||
| ``` | |||
| For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). | |||
| @@ -0,0 +1,41 @@ | |||
| 'use strict' | |||
| require('./check-versions')() | |||
| process.env.NODE_ENV = 'production' | |||
| const ora = require('ora') | |||
| const rm = require('rimraf') | |||
| const path = require('path') | |||
| const chalk = require('chalk') | |||
| const webpack = require('webpack') | |||
| const config = require('../config') | |||
| const webpackConfig = require('./webpack.prod.conf') | |||
| const spinner = ora('building for production...') | |||
| spinner.start() | |||
| rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { | |||
| if (err) throw err | |||
| webpack(webpackConfig, (err, stats) => { | |||
| spinner.stop() | |||
| if (err) throw err | |||
| process.stdout.write(stats.toString({ | |||
| colors: true, | |||
| modules: false, | |||
| children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. | |||
| chunks: false, | |||
| chunkModules: false | |||
| }) + '\n\n') | |||
| if (stats.hasErrors()) { | |||
| console.log(chalk.red(' Build failed with errors.\n')) | |||
| process.exit(1) | |||
| } | |||
| console.log(chalk.cyan(' Build complete.\n')) | |||
| console.log(chalk.yellow( | |||
| ' Tip: built files are meant to be served over an HTTP server.\n' + | |||
| ' Opening index.html over file:// won\'t work.\n' | |||
| )) | |||
| }) | |||
| }) | |||
| @@ -0,0 +1,54 @@ | |||
| 'use strict' | |||
| const chalk = require('chalk') | |||
| const semver = require('semver') | |||
| const packageConfig = require('../package.json') | |||
| const shell = require('shelljs') | |||
| function exec (cmd) { | |||
| return require('child_process').execSync(cmd).toString().trim() | |||
| } | |||
| const versionRequirements = [ | |||
| { | |||
| name: 'node', | |||
| currentVersion: semver.clean(process.version), | |||
| versionRequirement: packageConfig.engines.node | |||
| } | |||
| ] | |||
| if (shell.which('npm')) { | |||
| versionRequirements.push({ | |||
| name: 'npm', | |||
| currentVersion: exec('npm --version'), | |||
| versionRequirement: packageConfig.engines.npm | |||
| }) | |||
| } | |||
| module.exports = function () { | |||
| const warnings = [] | |||
| for (let i = 0; i < versionRequirements.length; i++) { | |||
| const mod = versionRequirements[i] | |||
| if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { | |||
| warnings.push(mod.name + ': ' + | |||
| chalk.red(mod.currentVersion) + ' should be ' + | |||
| chalk.green(mod.versionRequirement) | |||
| ) | |||
| } | |||
| } | |||
| if (warnings.length) { | |||
| console.log('') | |||
| console.log(chalk.yellow('To use this template, you must update following to modules:')) | |||
| console.log() | |||
| for (let i = 0; i < warnings.length; i++) { | |||
| const warning = warnings[i] | |||
| console.log(' ' + warning) | |||
| } | |||
| console.log() | |||
| process.exit(1) | |||
| } | |||
| } | |||
| @@ -0,0 +1,101 @@ | |||
| 'use strict' | |||
| const path = require('path') | |||
| const config = require('../config') | |||
| const ExtractTextPlugin = require('extract-text-webpack-plugin') | |||
| const packageConfig = require('../package.json') | |||
| exports.assetsPath = function (_path) { | |||
| const assetsSubDirectory = process.env.NODE_ENV === 'production' | |||
| ? config.build.assetsSubDirectory | |||
| : config.dev.assetsSubDirectory | |||
| return path.posix.join(assetsSubDirectory, _path) | |||
| } | |||
| exports.cssLoaders = function (options) { | |||
| options = options || {} | |||
| const cssLoader = { | |||
| loader: 'css-loader', | |||
| options: { | |||
| sourceMap: options.sourceMap | |||
| } | |||
| } | |||
| const postcssLoader = { | |||
| loader: 'postcss-loader', | |||
| options: { | |||
| sourceMap: options.sourceMap | |||
| } | |||
| } | |||
| // generate loader string to be used with extract text plugin | |||
| function generateLoaders (loader, loaderOptions) { | |||
| const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] | |||
| if (loader) { | |||
| loaders.push({ | |||
| loader: loader + '-loader', | |||
| options: Object.assign({}, loaderOptions, { | |||
| sourceMap: options.sourceMap | |||
| }) | |||
| }) | |||
| } | |||
| // Extract CSS when that option is specified | |||
| // (which is the case during production build) | |||
| if (options.extract) { | |||
| return ExtractTextPlugin.extract({ | |||
| use: loaders, | |||
| fallback: 'vue-style-loader' | |||
| }) | |||
| } else { | |||
| return ['vue-style-loader'].concat(loaders) | |||
| } | |||
| } | |||
| // https://vue-loader.vuejs.org/en/configurations/extract-css.html | |||
| return { | |||
| css: generateLoaders(), | |||
| postcss: generateLoaders(), | |||
| less: generateLoaders('less'), | |||
| sass: generateLoaders('sass', { indentedSyntax: true }), | |||
| scss: generateLoaders('sass'), | |||
| stylus: generateLoaders('stylus'), | |||
| styl: generateLoaders('stylus') | |||
| } | |||
| } | |||
| // Generate loaders for standalone style files (outside of .vue) | |||
| exports.styleLoaders = function (options) { | |||
| const output = [] | |||
| const loaders = exports.cssLoaders(options) | |||
| for (const extension in loaders) { | |||
| const loader = loaders[extension] | |||
| output.push({ | |||
| test: new RegExp('\\.' + extension + '$'), | |||
| use: loader | |||
| }) | |||
| } | |||
| return output | |||
| } | |||
| exports.createNotifierCallback = () => { | |||
| const notifier = require('node-notifier') | |||
| return (severity, errors) => { | |||
| if (severity !== 'error') return | |||
| const error = errors[0] | |||
| const filename = error.file && error.file.split('!').pop() | |||
| notifier.notify({ | |||
| title: packageConfig.name, | |||
| message: severity + ': ' + error.name, | |||
| subtitle: filename || '', | |||
| icon: path.join(__dirname, 'logo.png') | |||
| }) | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| 'use strict' | |||
| const utils = require('./utils') | |||
| const config = require('../config') | |||
| const isProduction = process.env.NODE_ENV === 'production' | |||
| const sourceMapEnabled = isProduction | |||
| ? config.build.productionSourceMap | |||
| : config.dev.cssSourceMap | |||
| module.exports = { | |||
| loaders: utils.cssLoaders({ | |||
| sourceMap: sourceMapEnabled, | |||
| extract: isProduction | |||
| }), | |||
| cssSourceMap: sourceMapEnabled, | |||
| cacheBusting: config.dev.cacheBusting, | |||
| transformToRequire: { | |||
| video: ['src', 'poster'], | |||
| source: 'src', | |||
| img: 'src', | |||
| image: 'xlink:href' | |||
| } | |||
| } | |||
| @@ -0,0 +1,86 @@ | |||
| 'use strict' | |||
| const path = require('path') | |||
| const utils = require('./utils') | |||
| const config = require('../config') | |||
| const vueLoaderConfig = require('./vue-loader.conf') | |||
| function resolve (dir) { | |||
| return path.join(__dirname, '..', dir) | |||
| } | |||
| module.exports = { | |||
| context: path.resolve(__dirname, '../'), | |||
| entry: { | |||
| app: './src/main.js' | |||
| }, | |||
| output: { | |||
| path: config.build.assetsRoot, | |||
| filename: '[name].js', | |||
| publicPath: process.env.NODE_ENV === 'production' | |||
| ? config.build.assetsPublicPath | |||
| : config.dev.assetsPublicPath | |||
| }, | |||
| resolve: { | |||
| extensions: ['.js', '.vue', '.json'], | |||
| alias: { | |||
| 'vue$': 'vue/dist/vue.esm.js', | |||
| '@': resolve('src'), | |||
| } | |||
| }, | |||
| module: { | |||
| rules: [ | |||
| { | |||
| test: /\.vue$/, | |||
| loader: 'vue-loader', | |||
| options: vueLoaderConfig | |||
| }, | |||
| { | |||
| test: /\.js$/, | |||
| loader: 'babel-loader', | |||
| include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] | |||
| }, | |||
| { | |||
| test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, | |||
| loader: 'url-loader', | |||
| options: { | |||
| limit: 10000, | |||
| name: utils.assetsPath('img/[name].[hash:7].[ext]') | |||
| } | |||
| }, | |||
| { | |||
| test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, | |||
| loader: 'url-loader', | |||
| options: { | |||
| limit: 10000, | |||
| name: utils.assetsPath('media/[name].[hash:7].[ext]') | |||
| } | |||
| }, | |||
| { | |||
| test: /\.scss$/, | |||
| loaders: ["style", "css", "sass"] | |||
| }, | |||
| { | |||
| test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, | |||
| loader: 'url-loader', | |||
| options: { | |||
| limit: 10000, | |||
| name: utils.assetsPath('fonts/[name].[hash:7].[ext]') | |||
| } | |||
| } | |||
| ] | |||
| }, | |||
| node: { | |||
| // prevent webpack from injecting useless setImmediate polyfill because Vue | |||
| // source contains it (although only uses it if it's native). | |||
| setImmediate: false, | |||
| // prevent webpack from injecting mocks to Node native modules | |||
| // that does not make sense for the client | |||
| dgram: 'empty', | |||
| fs: 'empty', | |||
| net: 'empty', | |||
| tls: 'empty', | |||
| child_process: 'empty' | |||
| } | |||
| } | |||
| @@ -0,0 +1,95 @@ | |||
| 'use strict' | |||
| const utils = require('./utils') | |||
| const webpack = require('webpack') | |||
| const config = require('../config') | |||
| const merge = require('webpack-merge') | |||
| const path = require('path') | |||
| const baseWebpackConfig = require('./webpack.base.conf') | |||
| const CopyWebpackPlugin = require('copy-webpack-plugin') | |||
| const HtmlWebpackPlugin = require('html-webpack-plugin') | |||
| const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') | |||
| const portfinder = require('portfinder') | |||
| const HOST = process.env.HOST | |||
| const PORT = process.env.PORT && Number(process.env.PORT) | |||
| const devWebpackConfig = merge(baseWebpackConfig, { | |||
| module: { | |||
| rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) | |||
| }, | |||
| // cheap-module-eval-source-map is faster for development | |||
| devtool: config.dev.devtool, | |||
| // these devServer options should be customized in /config/index.js | |||
| devServer: { | |||
| clientLogLevel: 'warning', | |||
| historyApiFallback: { | |||
| rewrites: [ | |||
| { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, | |||
| ], | |||
| }, | |||
| hot: true, | |||
| contentBase: false, // since we use CopyWebpackPlugin. | |||
| compress: true, | |||
| host: HOST || config.dev.host, | |||
| port: PORT || config.dev.port, | |||
| open: config.dev.autoOpenBrowser, | |||
| overlay: config.dev.errorOverlay | |||
| ? { warnings: false, errors: true } | |||
| : false, | |||
| publicPath: config.dev.assetsPublicPath, | |||
| proxy: config.dev.proxyTable, | |||
| quiet: true, // necessary for FriendlyErrorsPlugin | |||
| watchOptions: { | |||
| poll: config.dev.poll, | |||
| } | |||
| }, | |||
| plugins: [ | |||
| new webpack.DefinePlugin({ | |||
| 'process.env': require('../config/dev.env') | |||
| }), | |||
| new webpack.HotModuleReplacementPlugin(), | |||
| new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. | |||
| new webpack.NoEmitOnErrorsPlugin(), | |||
| // https://github.com/ampedandwired/html-webpack-plugin | |||
| new HtmlWebpackPlugin({ | |||
| filename: 'index.html', | |||
| template: 'index.html', | |||
| inject: true | |||
| }), | |||
| // copy custom static assets | |||
| new CopyWebpackPlugin([ | |||
| { | |||
| from: path.resolve(__dirname, '../static'), | |||
| to: config.dev.assetsSubDirectory, | |||
| ignore: ['.*'] | |||
| } | |||
| ]) | |||
| ] | |||
| }) | |||
| module.exports = new Promise((resolve, reject) => { | |||
| portfinder.basePort = process.env.PORT || config.dev.port | |||
| portfinder.getPort((err, port) => { | |||
| if (err) { | |||
| reject(err) | |||
| } else { | |||
| // publish the new Port, necessary for e2e tests | |||
| process.env.PORT = port | |||
| // add port to devServer config | |||
| devWebpackConfig.devServer.port = port | |||
| // Add FriendlyErrorsPlugin | |||
| devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ | |||
| compilationSuccessInfo: { | |||
| messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], | |||
| }, | |||
| onErrors: config.dev.notifyOnErrors | |||
| ? utils.createNotifierCallback() | |||
| : undefined | |||
| })) | |||
| resolve(devWebpackConfig) | |||
| } | |||
| }) | |||
| }) | |||
| @@ -0,0 +1,145 @@ | |||
| 'use strict' | |||
| const path = require('path') | |||
| const utils = require('./utils') | |||
| const webpack = require('webpack') | |||
| const config = require('../config') | |||
| const merge = require('webpack-merge') | |||
| const baseWebpackConfig = require('./webpack.base.conf') | |||
| const CopyWebpackPlugin = require('copy-webpack-plugin') | |||
| const HtmlWebpackPlugin = require('html-webpack-plugin') | |||
| const ExtractTextPlugin = require('extract-text-webpack-plugin') | |||
| const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') | |||
| const UglifyJsPlugin = require('uglifyjs-webpack-plugin') | |||
| const env = require('../config/prod.env') | |||
| const webpackConfig = merge(baseWebpackConfig, { | |||
| module: { | |||
| rules: utils.styleLoaders({ | |||
| sourceMap: config.build.productionSourceMap, | |||
| extract: true, | |||
| usePostCSS: true | |||
| }) | |||
| }, | |||
| devtool: config.build.productionSourceMap ? config.build.devtool : false, | |||
| output: { | |||
| path: config.build.assetsRoot, | |||
| filename: utils.assetsPath('js/[name].[chunkhash].js'), | |||
| chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') | |||
| }, | |||
| plugins: [ | |||
| // http://vuejs.github.io/vue-loader/en/workflow/production.html | |||
| new webpack.DefinePlugin({ | |||
| 'process.env': env | |||
| }), | |||
| new UglifyJsPlugin({ | |||
| uglifyOptions: { | |||
| compress: { | |||
| warnings: false | |||
| } | |||
| }, | |||
| sourceMap: config.build.productionSourceMap, | |||
| parallel: true | |||
| }), | |||
| // extract css into its own file | |||
| new ExtractTextPlugin({ | |||
| filename: utils.assetsPath('css/[name].[contenthash].css'), | |||
| // Setting the following option to `false` will not extract CSS from codesplit chunks. | |||
| // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. | |||
| // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, | |||
| // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 | |||
| allChunks: true, | |||
| }), | |||
| // Compress extracted CSS. We are using this plugin so that possible | |||
| // duplicated CSS from different components can be deduped. | |||
| new OptimizeCSSPlugin({ | |||
| cssProcessorOptions: config.build.productionSourceMap | |||
| ? { safe: true, map: { inline: false } } | |||
| : { safe: true } | |||
| }), | |||
| // generate dist index.html with correct asset hash for caching. | |||
| // you can customize output by editing /index.html | |||
| // see https://github.com/ampedandwired/html-webpack-plugin | |||
| new HtmlWebpackPlugin({ | |||
| filename: config.build.index, | |||
| template: 'index.html', | |||
| inject: true, | |||
| minify: { | |||
| removeComments: true, | |||
| collapseWhitespace: true, | |||
| removeAttributeQuotes: true | |||
| // more options: | |||
| // https://github.com/kangax/html-minifier#options-quick-reference | |||
| }, | |||
| // necessary to consistently work with multiple chunks via CommonsChunkPlugin | |||
| chunksSortMode: 'dependency' | |||
| }), | |||
| // keep module.id stable when vendor modules does not change | |||
| new webpack.HashedModuleIdsPlugin(), | |||
| // enable scope hoisting | |||
| new webpack.optimize.ModuleConcatenationPlugin(), | |||
| // split vendor js into its own file | |||
| new webpack.optimize.CommonsChunkPlugin({ | |||
| name: 'vendor', | |||
| minChunks (module) { | |||
| // any required modules inside node_modules are extracted to vendor | |||
| return ( | |||
| module.resource && | |||
| /\.js$/.test(module.resource) && | |||
| module.resource.indexOf( | |||
| path.join(__dirname, '../node_modules') | |||
| ) === 0 | |||
| ) | |||
| } | |||
| }), | |||
| // extract webpack runtime and module manifest to its own file in order to | |||
| // prevent vendor hash from being updated whenever app bundle is updated | |||
| new webpack.optimize.CommonsChunkPlugin({ | |||
| name: 'manifest', | |||
| minChunks: Infinity | |||
| }), | |||
| // This instance extracts shared chunks from code splitted chunks and bundles them | |||
| // in a separate chunk, similar to the vendor chunk | |||
| // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk | |||
| new webpack.optimize.CommonsChunkPlugin({ | |||
| name: 'app', | |||
| async: 'vendor-async', | |||
| children: true, | |||
| minChunks: 3 | |||
| }), | |||
| // copy custom static assets | |||
| new CopyWebpackPlugin([ | |||
| { | |||
| from: path.resolve(__dirname, '../static'), | |||
| to: config.build.assetsSubDirectory, | |||
| ignore: ['.*'] | |||
| } | |||
| ]) | |||
| ] | |||
| }) | |||
| if (config.build.productionGzip) { | |||
| const CompressionWebpackPlugin = require('compression-webpack-plugin') | |||
| webpackConfig.plugins.push( | |||
| new CompressionWebpackPlugin({ | |||
| asset: '[path].gz[query]', | |||
| algorithm: 'gzip', | |||
| test: new RegExp( | |||
| '\\.(' + | |||
| config.build.productionGzipExtensions.join('|') + | |||
| ')$' | |||
| ), | |||
| threshold: 10240, | |||
| minRatio: 0.8 | |||
| }) | |||
| ) | |||
| } | |||
| if (config.build.bundleAnalyzerReport) { | |||
| const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin | |||
| webpackConfig.plugins.push(new BundleAnalyzerPlugin()) | |||
| } | |||
| module.exports = webpackConfig | |||
| @@ -0,0 +1,8 @@ | |||
| 'use strict' | |||
| const merge = require('webpack-merge') | |||
| const prodEnv = require('./prod.env') | |||
| module.exports = merge(prodEnv, { | |||
| NODE_ENV: '"development"', | |||
| VUE_APP_BASE_API:'"/dev-api"' | |||
| }) | |||
| @@ -0,0 +1,76 @@ | |||
| 'use strict' | |||
| // Template version: 1.3.1 | |||
| // see http://vuejs-templates.github.io/webpack for documentation. | |||
| const path = require('path') | |||
| module.exports = { | |||
| dev: { | |||
| // Paths | |||
| assetsSubDirectory: 'static', | |||
| assetsPublicPath: '/', | |||
| proxyTable: { | |||
| "/dev-api": { | |||
| // 请求的目标主机 | |||
| target: 'http://testf.5256.xyz/ruoyi-admin/', | |||
| changeOrigin: true, | |||
| pathRewrite: { | |||
| '^/dev-api': '' | |||
| } | |||
| } | |||
| }, | |||
| // Various Dev Server settings | |||
| host: 'localhost', // can be overwritten by process.env.HOST | |||
| port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined | |||
| autoOpenBrowser: false, | |||
| errorOverlay: true, | |||
| notifyOnErrors: true, | |||
| poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- | |||
| /** | |||
| * Source Maps | |||
| */ | |||
| // https://webpack.js.org/configuration/devtool/#development | |||
| devtool: 'cheap-module-eval-source-map', | |||
| // If you have problems debugging vue-files in devtools, | |||
| // set this to false - it *may* help | |||
| // https://vue-loader.vuejs.org/en/options.html#cachebusting | |||
| cacheBusting: true, | |||
| cssSourceMap: true | |||
| }, | |||
| build: { | |||
| // Template for index.html | |||
| index: path.resolve(__dirname, '../dist/index.html'), | |||
| // Paths | |||
| assetsRoot: path.resolve(__dirname, '../dist'), | |||
| assetsSubDirectory: 'static', | |||
| assetsPublicPath: '/', | |||
| /** | |||
| * Source Maps | |||
| */ | |||
| productionSourceMap: true, | |||
| // https://webpack.js.org/configuration/devtool/#production | |||
| devtool: '#source-map', | |||
| // Gzip off by default as many popular static hosts such as | |||
| // Surge or Netlify already gzip all static assets for you. | |||
| // Before setting to `true`, make sure to: | |||
| // npm install --save-dev compression-webpack-plugin | |||
| productionGzip: false, | |||
| productionGzipExtensions: ['js', 'css'], | |||
| // Run the build command with an extra argument to | |||
| // View the bundle analyzer report after build finishes: | |||
| // `npm run build --report` | |||
| // Set to `true` or `false` to always turn it on or off | |||
| bundleAnalyzerReport: process.env.npm_config_report | |||
| } | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| 'use strict' | |||
| module.exports = { | |||
| NODE_ENV: '"production"' | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,minimum-scale=1.0,maximum=scale=1.0" /> | |||
| <title>nsgk_frame</title> | |||
| </head> | |||
| <body> | |||
| <div id="app"></div> | |||
| <!-- built files will be auto injected --> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,75 @@ | |||
| { | |||
| "name": "nsgk_frame", | |||
| "version": "1.0.0", | |||
| "description": "", | |||
| "author": "huhan <huhankx@qq.com>", | |||
| "private": true, | |||
| "scripts": { | |||
| "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", | |||
| "start": "npm run dev", | |||
| "build": "node build/build.js" | |||
| }, | |||
| "dependencies": { | |||
| "axios": "^0.21.1", | |||
| "js-cookie": "^2.2.1", | |||
| "lib-flexible": "^0.3.2", | |||
| "node-sass": "^5.0.0", | |||
| "nprogress": "^0.2.0", | |||
| "sass": "^1.32.8", | |||
| "sass-loader": "^6.0.6", | |||
| "vant": "^2.12.8", | |||
| "vue": "^2.5.2", | |||
| "vue-router": "^3.0.1", | |||
| "vuex": "^3.6.2" | |||
| }, | |||
| "devDependencies": { | |||
| "amfe-flexible": "^2.2.1", | |||
| "autoprefixer": "^7.1.2", | |||
| "babel-core": "^6.22.1", | |||
| "babel-helper-vue-jsx-merge-props": "^2.0.3", | |||
| "babel-loader": "^7.1.1", | |||
| "babel-plugin-syntax-jsx": "^6.18.0", | |||
| "babel-plugin-transform-runtime": "^6.22.0", | |||
| "babel-plugin-transform-vue-jsx": "^3.5.0", | |||
| "babel-preset-env": "^1.3.2", | |||
| "babel-preset-stage-2": "^6.22.0", | |||
| "chalk": "^2.0.1", | |||
| "copy-webpack-plugin": "^4.0.1", | |||
| "css-loader": "^0.28.0", | |||
| "extract-text-webpack-plugin": "^3.0.0", | |||
| "fastclick": "^1.0.6", | |||
| "file-loader": "^1.1.4", | |||
| "friendly-errors-webpack-plugin": "^1.6.1", | |||
| "html-webpack-plugin": "^2.30.1", | |||
| "node-notifier": "^5.1.2", | |||
| "optimize-css-assets-webpack-plugin": "^3.2.0", | |||
| "ora": "^1.2.0", | |||
| "portfinder": "^1.0.13", | |||
| "postcss-import": "^11.0.0", | |||
| "postcss-loader": "^2.0.8", | |||
| "postcss-px2rem": "^0.3.0", | |||
| "postcss-pxtorem": "^6.0.0", | |||
| "postcss-url": "^7.2.1", | |||
| "rimraf": "^2.6.0", | |||
| "semver": "^5.3.0", | |||
| "shelljs": "^0.7.6", | |||
| "uglifyjs-webpack-plugin": "^1.1.1", | |||
| "url-loader": "^0.5.8", | |||
| "vue-loader": "^13.3.0", | |||
| "vue-style-loader": "^3.0.1", | |||
| "vue-template-compiler": "^2.5.2", | |||
| "webpack": "^3.6.0", | |||
| "webpack-bundle-analyzer": "^2.9.0", | |||
| "webpack-dev-server": "^2.9.1", | |||
| "webpack-merge": "^4.1.0" | |||
| }, | |||
| "engines": { | |||
| "node": ">= 6.0.0", | |||
| "npm": ">= 3.0.0" | |||
| }, | |||
| "browserslist": [ | |||
| "> 1%", | |||
| "last 2 versions", | |||
| "not ie <= 8" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| const autoprefixer = require('autoprefixer'); | |||
| const pxtorem = require('postcss-pxtorem'); | |||
| module.exports = ({ file }) => { | |||
| let remUnit | |||
| if (file && file.dirname && file.dirname.indexOf('vant') > -1) { | |||
| remUnit = 37.5 | |||
| } else { | |||
| remUnit = 75 | |||
| } | |||
| return { | |||
| plugins: [ | |||
| autoprefixer(), | |||
| pxtorem({ | |||
| rootValue: remUnit, | |||
| propList: ['*'], | |||
| selectorBlackList: ['van-circle__layer'] | |||
| }) | |||
| ] | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| <template> | |||
| <div class="nsgk-global-main"> | |||
| <router-view/> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| name: 'App' | |||
| } | |||
| </script> | |||
| <style lang="scss" > | |||
| @import './assets/css/base' | |||
| </style> | |||
| @@ -0,0 +1,67 @@ | |||
| import request from '@/utils/request' | |||
| // 登录方法 | |||
| export function login(username, password, code, uuid) { | |||
| const data = { | |||
| username, | |||
| password, | |||
| code, | |||
| uuid | |||
| } | |||
| return request({ | |||
| url: '/login', | |||
| method: 'post', | |||
| data: data | |||
| }) | |||
| } | |||
| // 短信登录方法 | |||
| export function smsLogin(mobile, smsCode, uuid) { | |||
| const data = { | |||
| mobile, | |||
| smsCode, | |||
| uuid | |||
| } | |||
| return request({ | |||
| url: '/sms/login', | |||
| method: 'post', | |||
| data: data | |||
| }) | |||
| } | |||
| // 获取用户详细信息 | |||
| export function getInfo() { | |||
| return request({ | |||
| url: '/getInfo', | |||
| method: 'get' | |||
| }) | |||
| } | |||
| // 退出方法 | |||
| export function logout() { | |||
| return request({ | |||
| url: '/logout', | |||
| method: 'post' | |||
| }) | |||
| } | |||
| // 获取验证码 | |||
| export function getCodeImg() { | |||
| return request({ | |||
| url: '/captchaImage', | |||
| method: 'get' | |||
| }) | |||
| } | |||
| // 发送短信验证码 | |||
| export function getSmsCode(mobile) { | |||
| const data = { | |||
| mobile | |||
| } | |||
| return request({ | |||
| url: '/sms/code', | |||
| method: 'post', | |||
| data:data | |||
| }) | |||
| } | |||
| @@ -0,0 +1,93 @@ | |||
| body, div, span, header, footer, nav, section, aside, article, ul, dl, dt, dd, li, a, p, h1, h2, h3, h4,h5, h6, i, b, textarea, button, input, select, figure, figcaption { | |||
| padding: 0; | |||
| margin: 0; | |||
| list-style: none; | |||
| font-style: normal; | |||
| text-decoration: none; | |||
| border: none; | |||
| box-sizing: border-box; | |||
| font-family: "Microsoft Yahei",sans-serif; | |||
| -webkit-tap-highlight-color:transparent; | |||
| -webkit-font-smoothing: antialiased; | |||
| &:focus { | |||
| outline: none; | |||
| } | |||
| } | |||
| /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ | |||
| ::-webkit-scrollbar | |||
| { | |||
| width: 0px; | |||
| height: 0px; | |||
| background-color: #F5F5F5; | |||
| } | |||
| /*定义滚动条轨道 内阴影+圆角*/ | |||
| ::-webkit-scrollbar-track | |||
| { | |||
| -webkit-box-shadow: inset 0 0 1px rgba(0,0,0,0); | |||
| border-radius: 10px; | |||
| background-color: #F5F5F5; | |||
| } | |||
| /*定义滑块 内阴影+圆角*/ | |||
| ::-webkit-scrollbar-thumb | |||
| { | |||
| border-radius: 10px; | |||
| -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); | |||
| background-color: #555; | |||
| } | |||
| input[type="button"], input[type="submit"], input[type="search"], input[type="reset"] { | |||
| -webkit-appearance: none; | |||
| } | |||
| textarea { -webkit-appearance: none;} | |||
| html,body{ | |||
| height: 100%; | |||
| width: 100%; | |||
| background-color: #F5F5F5; | |||
| } | |||
| .clear:after{ | |||
| content: ''; | |||
| display: block; | |||
| clear: both; | |||
| } | |||
| .clear{ | |||
| zoom:1; | |||
| } | |||
| .back_img{ | |||
| background-repeat: no-repeat; | |||
| background-size: 100% 100%; | |||
| } | |||
| .margin{ | |||
| margin: 0 auto; | |||
| } | |||
| .left{ | |||
| float: left; | |||
| } | |||
| .right{ | |||
| float: right; | |||
| } | |||
| .hide{ | |||
| display: none; | |||
| } | |||
| .show{ | |||
| display: block; | |||
| } | |||
| .ellipsis{ | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| } | |||
| @@ -0,0 +1,118 @@ | |||
| ;(function(win, lib) { | |||
| var doc = win.document; | |||
| var docEl = doc.documentElement; | |||
| var metaEl = doc.querySelector('meta[name="viewport"]'); | |||
| var flexibleEl = doc.querySelector('meta[name="flexible"]'); | |||
| var dpr = 0; | |||
| var scale = 0; | |||
| var tid; | |||
| var flexible = lib.flexible || (lib.flexible = {}); | |||
| if (metaEl) { | |||
| console.warn('将根据已有的meta标签来设置缩放比例'); | |||
| var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/); | |||
| if (match) { | |||
| scale = parseFloat(match[1]); | |||
| dpr = parseInt(1 / scale); | |||
| } | |||
| } else if (flexibleEl) { | |||
| var content = flexibleEl.getAttribute('content'); | |||
| if (content) { | |||
| var initialDpr = content.match(/initial\-dpr=([\d\.]+)/); | |||
| var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/); | |||
| if (initialDpr) { | |||
| dpr = parseFloat(initialDpr[1]); | |||
| scale = parseFloat((1 / dpr).toFixed(2)); | |||
| } | |||
| if (maximumDpr) { | |||
| dpr = parseFloat(maximumDpr[1]); | |||
| scale = parseFloat((1 / dpr).toFixed(2)); | |||
| } | |||
| } | |||
| } | |||
| if (!dpr && !scale) { | |||
| var isAndroid = win.navigator.appVersion.match(/android/gi); | |||
| var isIPhone = win.navigator.appVersion.match(/iphone/gi); | |||
| var devicePixelRatio = win.devicePixelRatio; | |||
| if (isIPhone) { | |||
| // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案 | |||
| if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { | |||
| dpr = 3; | |||
| } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ | |||
| dpr = 2; | |||
| } else { | |||
| dpr = 1; | |||
| } | |||
| } else { | |||
| // 其他设备下,仍旧使用1倍的方案 | |||
| dpr = 1; | |||
| } | |||
| scale = 1 / dpr; | |||
| } | |||
| docEl.setAttribute('data-dpr', dpr); | |||
| if (!metaEl) { | |||
| metaEl = doc.createElement('meta'); | |||
| metaEl.setAttribute('name', 'viewport'); | |||
| metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); | |||
| if (docEl.firstElementChild) { | |||
| docEl.firstElementChild.appendChild(metaEl); | |||
| } else { | |||
| var wrap = doc.createElement('div'); | |||
| wrap.appendChild(metaEl); | |||
| doc.write(wrap.innerHTML); | |||
| } | |||
| } | |||
| function refreshRem(){ | |||
| var width = docEl.getBoundingClientRect().width; | |||
| if (width / dpr > 980) { | |||
| width = 980 * dpr; | |||
| } | |||
| var rem = width / 10; | |||
| docEl.style.fontSize = rem + 'px'; | |||
| flexible.rem = win.rem = rem; | |||
| } | |||
| win.addEventListener('resize', function() { | |||
| clearTimeout(tid); | |||
| tid = setTimeout(refreshRem, 300); | |||
| }, false); | |||
| win.addEventListener('pageshow', function(e) { | |||
| if (e.persisted) { | |||
| clearTimeout(tid); | |||
| tid = setTimeout(refreshRem, 300); | |||
| } | |||
| }, false); | |||
| if (doc.readyState === 'complete') { | |||
| doc.body.style.fontSize = 12 * dpr + 'px'; | |||
| } else { | |||
| doc.addEventListener('DOMContentLoaded', function(e) { | |||
| doc.body.style.fontSize = 12 * dpr + 'px'; | |||
| }, false); | |||
| } | |||
| refreshRem(); | |||
| flexible.dpr = win.dpr = dpr; | |||
| flexible.refreshRem = refreshRem; | |||
| flexible.rem2px = function(d) { | |||
| var val = parseFloat(d) * this.rem; | |||
| if (typeof d === 'string' && d.match(/rem$/)) { | |||
| val += 'px'; | |||
| } | |||
| return val; | |||
| } | |||
| flexible.px2rem = function(d) { | |||
| var val = parseFloat(d) / this.rem; | |||
| if (typeof d === 'string' && d.match(/px$/)) { | |||
| val += 'rem'; | |||
| } | |||
| return val; | |||
| } | |||
| })(window, window['lib'] || (window['lib'] = {})); | |||
| @@ -0,0 +1,33 @@ | |||
| // The Vue build version to load with the `import` command | |||
| // (runtime-only or standalone) has been set in webpack.base.conf with an alias. | |||
| import Vue from 'vue' | |||
| import App from './App' | |||
| import router from './router' | |||
| import FastClick from 'fastclick' | |||
| Vue.config.productionTip = false | |||
| // import 'lib-flexible/flexible.js' | |||
| import 'amfe-flexible/index.js' | |||
| import './config/flexible' | |||
| import store from './store/' | |||
| import './permission' // permission control | |||
| // Vant 引用 | |||
| import Vant from 'vant'; | |||
| import 'vant/lib/index.css'; | |||
| Vue.use(Vant) | |||
| /*解决手动点击与真正触发click事件会存在300ms的延迟*/ | |||
| if ('addEventListener' in document) { | |||
| document.addEventListener('DOMContentLoaded', function() { | |||
| FastClick.attach(document.body); | |||
| }, false); | |||
| } | |||
| /* eslint-disable no-new */ | |||
| new Vue({ | |||
| el: '#app', | |||
| router, | |||
| store, | |||
| components: { App }, | |||
| template: '<App/>' | |||
| }) | |||
| @@ -0,0 +1,54 @@ | |||
| import router from './router' | |||
| import store from './store' | |||
| // import { Message } from 'element-ui' | |||
| import NProgress from 'nprogress' | |||
| import 'nprogress/nprogress.css' | |||
| import { getToken } from '@/utils/auth' | |||
| NProgress.configure({ showSpinner: false }) | |||
| const whiteList = ['/login', '/auth-redirect', '/bind', '/register'] | |||
| router.beforeEach((to, from, next) => { | |||
| NProgress.start() | |||
| if (getToken()) { | |||
| /* has token*/ | |||
| if (to.path === '/login') { | |||
| next({ path: '/' }) | |||
| NProgress.done() | |||
| } else { | |||
| if (store.getters.roles.length === 0) { | |||
| // 判断当前用户是否已拉取完user_info信息 | |||
| store.dispatch('GetInfo').then(res => { | |||
| // 拉取user_info | |||
| const roles = res.roles | |||
| store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => { | |||
| // 根据roles权限生成可访问的路由表 | |||
| router.addRoutes(accessRoutes) // 动态添加可访问路由表 | |||
| next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 | |||
| }) | |||
| }).catch(err => { | |||
| store.dispatch('LogOut').then(() => { | |||
| // Message.error(err) | |||
| next({ path: '/' }) | |||
| }) | |||
| }) | |||
| } else { | |||
| next() | |||
| } | |||
| } | |||
| } else { | |||
| // 没有token | |||
| if (whiteList.indexOf(to.path) !== -1) { | |||
| // 在免登录白名单,直接进入 | |||
| next() | |||
| } else { | |||
| next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 | |||
| NProgress.done() | |||
| } | |||
| } | |||
| }) | |||
| router.afterEach(() => { | |||
| NProgress.done() | |||
| }) | |||
| @@ -0,0 +1,25 @@ | |||
| import Vue from 'vue' | |||
| import Router from 'vue-router' | |||
| Vue.use(Router) | |||
| export const constantRoutes =[ | |||
| { | |||
| path: '/', | |||
| name: 'index', | |||
| component: (resolve) => require(['@/views/home'], resolve) | |||
| }, | |||
| { | |||
| path: '/login', | |||
| name: 'login', | |||
| component: (resolve) => require(['@/views/login'], resolve) | |||
| } | |||
| ]; | |||
| export default new Router({ | |||
| mode: 'history', // 去掉url中的# | |||
| scrollBehavior: () => ({ y: 0 }), | |||
| routes: constantRoutes | |||
| }) | |||
| @@ -0,0 +1,11 @@ | |||
| const getters = { | |||
| token: state => state.user.token, | |||
| avatar: state => state.user.avatar, | |||
| name: state => state.user.name, | |||
| introduction: state => state.user.introduction, | |||
| roles: state => state.user.roles, | |||
| permissions: state => state.user.permissions, | |||
| bookName: state => state.user.bookName, | |||
| } | |||
| export default getters | |||
| @@ -0,0 +1,15 @@ | |||
| import Vue from 'vue' | |||
| import Vuex from 'vuex' | |||
| import app from './modules/user' | |||
| import getters from './getters' | |||
| Vue.use(Vuex) | |||
| const store = new Vuex.Store({ | |||
| modules: { | |||
| app, | |||
| }, | |||
| getters | |||
| }) | |||
| export default store | |||
| @@ -0,0 +1,134 @@ | |||
| import { login, logout, getInfo, smsLogin } from '@/api/login' | |||
| import { getToken, setToken, removeToken } from '@/utils/auth' | |||
| const user = { | |||
| state: { | |||
| token: getToken(), | |||
| name: '', | |||
| avatar: '', | |||
| roles: [], | |||
| permissions: [], | |||
| bookName: '' | |||
| }, | |||
| mutations: { | |||
| SET_TOKEN: (state, token) => { | |||
| state.token = token | |||
| }, | |||
| SET_NAME: (state, name) => { | |||
| state.name = name | |||
| }, | |||
| SET_AVATAR: (state, avatar) => { | |||
| state.avatar = avatar | |||
| }, | |||
| SET_ROLES: (state, roles) => { | |||
| state.roles = roles | |||
| }, | |||
| SET_PERMISSIONS: (state, permissions) => { | |||
| state.permissions = permissions | |||
| }, | |||
| SET_LOGINDEPTID: (state, loginDeptId) => { | |||
| state.loginDeptId = loginDeptId | |||
| }, | |||
| SET_LOGINBOOKID: (state, loginBookId) => { | |||
| state.loginBookId = loginBookId | |||
| }, | |||
| SET_DEPTNAME: (state, deptName) => { | |||
| state.deptName = deptName | |||
| }, | |||
| SET_BOOKNAME: (state, bookName) => { | |||
| state.bookName = bookName | |||
| } | |||
| }, | |||
| actions: { | |||
| // 登录 | |||
| Login({ commit }, userInfo) { | |||
| const username = userInfo.username.trim() | |||
| const password = userInfo.password | |||
| const code = userInfo.code | |||
| const uuid = userInfo.uuid | |||
| return new Promise((resolve, reject) => { | |||
| login(username, password, code, uuid).then(res => { | |||
| setToken(res.token) | |||
| commit('SET_TOKEN', res.token) | |||
| resolve() | |||
| }).catch(error => { | |||
| reject(error) | |||
| }) | |||
| }) | |||
| }, | |||
| //短信验证码登录 | |||
| SmsLogin({ commit }, userInfo) { | |||
| const mobile = userInfo.mobile.trim() | |||
| const smsCode = userInfo.smsCode | |||
| const uuid = userInfo.uuid | |||
| return new Promise((resolve, reject) => { | |||
| smsLogin(mobile, smsCode, uuid).then(res => { | |||
| setToken(res.token) | |||
| commit('SET_TOKEN', res.token) | |||
| resolve() | |||
| }).catch(error => { | |||
| reject(error) | |||
| }) | |||
| }) | |||
| }, | |||
| // 获取用户信息 | |||
| GetInfo({ commit, state }) { | |||
| return new Promise((resolve, reject) => { | |||
| getInfo(state.token).then(res => { | |||
| const user = res.user | |||
| // const avatar = user.avatar == "" ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar; | |||
| const avatar = ""; | |||
| if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 | |||
| commit('SET_ROLES', res.roles) | |||
| commit('SET_PERMISSIONS', res.permissions) | |||
| } else { | |||
| commit('SET_ROLES', ['ROLE_DEFAULT']) | |||
| } | |||
| commit('SET_BOOKNAME', user.bookName) | |||
| commit('SET_NAME', user.userName) | |||
| commit('SET_LOGINDEPTID', user.loginDeptId) | |||
| commit('SET_LOGINBOOKID', user.loginBookId) | |||
| commit('SET_DEPTNAME', user.deptName) | |||
| commit('SET_AVATAR', avatar) | |||
| resolve(res) | |||
| }).catch(error => { | |||
| reject(error) | |||
| }) | |||
| }) | |||
| }, | |||
| // 退出系统 | |||
| LogOut({ commit, state }) { | |||
| return new Promise((resolve, reject) => { | |||
| logout(state.token).then(() => { | |||
| commit('SET_TOKEN', '') | |||
| commit('SET_ROLES', []) | |||
| commit('SET_PERMISSIONS', []) | |||
| removeToken() | |||
| resolve() | |||
| }).catch(error => { | |||
| reject(error) | |||
| }) | |||
| }) | |||
| }, | |||
| // 前端 登出 | |||
| FedLogOut({ commit }) { | |||
| return new Promise(resolve => { | |||
| commit('SET_TOKEN', '') | |||
| removeToken() | |||
| resolve() | |||
| }) | |||
| }, | |||
| // 更新用户信息 | |||
| } | |||
| } | |||
| export default user | |||
| @@ -0,0 +1,15 @@ | |||
| import Cookies from 'js-cookie' | |||
| const TokenKey = 'User-Token' | |||
| export function getToken() { | |||
| return Cookies.get(TokenKey) | |||
| } | |||
| export function setToken(token) { | |||
| return Cookies.set(TokenKey, token) | |||
| } | |||
| export function removeToken() { | |||
| return Cookies.remove(TokenKey) | |||
| } | |||
| @@ -0,0 +1,6 @@ | |||
| export default { | |||
| '401': '认证失败,无法访问系统资源', | |||
| '403': '当前操作没有权限', | |||
| '404': '访问资源不存在', | |||
| 'default': '系统未知错误,请反馈给管理员' | |||
| } | |||
| @@ -0,0 +1,102 @@ | |||
| import axios from 'axios' | |||
| // import { Notification, MessageBox, Message } from 'element-ui' | |||
| import { Notify,Dialog } from 'vant'; | |||
| import store from '@/store' | |||
| import { getToken } from '@/utils/auth' | |||
| import errorCode from '@/utils/errorCode' | |||
| axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' | |||
| // 创建axios实例 | |||
| const service = axios.create({ | |||
| // axios中请求配置有baseURL选项,表示请求URL公共部分 | |||
| baseURL: process.env.VUE_APP_BASE_API, | |||
| // 超时 | |||
| timeout: 100000 | |||
| }) | |||
| // request拦截器 | |||
| service.interceptors.request.use(config => { | |||
| // 是否需要设置 token | |||
| const isToken = (config.headers || {}).isToken === false | |||
| if (getToken() && !isToken) { | |||
| config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 | |||
| } | |||
| // get请求映射params参数 | |||
| if (config.method === 'get' && config.params) { | |||
| let url = config.url + '?'; | |||
| for (const propName of Object.keys(config.params)) { | |||
| const value = config.params[propName]; | |||
| var part = encodeURIComponent(propName) + "="; | |||
| if (value !== null && typeof(value) !== "undefined") { | |||
| if (typeof value === 'object') { | |||
| for (const key of Object.keys(value)) { | |||
| let params = propName + '[' + key + ']'; | |||
| var subPart = encodeURIComponent(params) + "="; | |||
| url += subPart + encodeURIComponent(value[key]) + "&"; | |||
| } | |||
| } else { | |||
| url += part + encodeURIComponent(value) + "&"; | |||
| } | |||
| } | |||
| } | |||
| url = url.slice(0, -1); | |||
| config.params = {}; | |||
| config.url = url; | |||
| } | |||
| return config | |||
| }, error => { | |||
| console.log(error) | |||
| Promise.reject(error) | |||
| }) | |||
| // 响应拦截器 | |||
| service.interceptors.response.use(res => { | |||
| // 未设置状态码则默认成功状态 | |||
| const code = res.data.code || 200; | |||
| // 获取错误信息 | |||
| const msg = errorCode[code] || res.data.msg || errorCode['default'] | |||
| if (code === 401) { | |||
| Dialog.confirm({ | |||
| title: '系统提示', | |||
| message: '登录状态已过期,您可以继续留在该页面,或者重新登录', | |||
| confirmButtonText:'重新登录', | |||
| cancelButtonText:'取消' | |||
| }) | |||
| .then(() => { | |||
| store.dispatch('LogOut').then(() => { | |||
| location.href = '/index'; | |||
| }) | |||
| }) | |||
| } else if (code === 500) { | |||
| Notify({ type: 'warning', message: msg }); | |||
| return Promise.reject(new Error(msg)) | |||
| } else if (code !== 200) { | |||
| Notify({ type: 'warning', message: msg }); | |||
| return Promise.reject('error') | |||
| } else { | |||
| return res.data | |||
| } | |||
| }, | |||
| error => { | |||
| console.log('err' + error) | |||
| let { message } = error; | |||
| if (message == "Network Error") { | |||
| message = "后端接口连接异常"; | |||
| } | |||
| else if (message.includes("timeout")) { | |||
| message = "系统接口请求超时"; | |||
| } | |||
| else if (message.includes("Request failed with status code")) { | |||
| message = "系统接口" + message.substr(message.length - 3) + "异常"; | |||
| } | |||
| // Message({ | |||
| // message: message, | |||
| // type: 'error', | |||
| // duration: 5 * 1000 | |||
| // }) | |||
| Notify({ type: 'warning', message: message }); | |||
| return Promise.reject(error) | |||
| } | |||
| ) | |||
| export default service | |||
| @@ -0,0 +1,17 @@ | |||
| <template> | |||
| <div class="hello">适配移动端</div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| name: '主页', | |||
| data () { | |||
| return { | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| <style scoped lang="scss"> | |||
| </style> | |||
| @@ -0,0 +1,164 @@ | |||
| <template> | |||
| <div class="login_page"> | |||
| <div class="focus-logo"> | |||
| <img src="@/assets/images/login-logo.png" /> | |||
| </div> | |||
| <ul class="head_nav clear"> | |||
| <li @click="isSmsLogin=false" :class="{active:!isSmsLogin}"> | |||
| 账号登录 | |||
| </li> | |||
| <li @click="isSmsLogin=true" :class="{active:isSmsLogin}"> | |||
| 验证码登录 | |||
| </li> | |||
| </ul> | |||
| <div class="form_main"> | |||
| <van-form @submit="handleLogin"> | |||
| <van-field | |||
| v-model="formData.username" | |||
| name="username" | |||
| label="用户名" | |||
| placeholder="用户名" | |||
| v-if="!isSmsLogin" | |||
| /> | |||
| <van-field | |||
| v-model="formData.password" | |||
| type="password" | |||
| name="password" | |||
| label="密码" | |||
| placeholder="密码" | |||
| v-if="!isSmsLogin" | |||
| /> | |||
| <van-field | |||
| v-model="formData.code" | |||
| center | |||
| clearable | |||
| label="验证码" | |||
| placeholder="请输入图形验证码" | |||
| v-if="!isSmsLogin" | |||
| > | |||
| <template #button> | |||
| <img class="code-img" :src="codeUrl" @click="getCode"/> | |||
| </template> | |||
| </van-field> | |||
| <van-field v-model="formData.mobile" v-if="isSmsLogin" label="手机号" placeholder="请输入手机号码"/> | |||
| <van-field | |||
| v-model="formData.smsCode" | |||
| center | |||
| clearable | |||
| label="短信验证码" | |||
| placeholder="请输入短信验证码" | |||
| v-if="isSmsLogin" | |||
| > | |||
| <template #button> | |||
| <van-button size="small" type="primary" @click.native.prevent="getSmsCode">{{computeTime>0 ? `(${computeTime}s)已发送` : '获取验证码'}}</van-button> | |||
| </template> | |||
| </van-field> | |||
| <div class="form-submit"> | |||
| <van-button round block type="info" native-type="submit">登录</van-button> | |||
| </div> | |||
| </van-form> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getCodeImg , getSmsCode , smsLogin } from "@/api/login"; | |||
| export default { | |||
| name: 'login', | |||
| data () { | |||
| return { | |||
| formData:{ | |||
| username: null, //账号 | |||
| password:null, //密码 | |||
| code:null, //图片验证码 | |||
| uuid:null, //识别uuid | |||
| mobile:null, //手机号 | |||
| }, | |||
| codeUrl:'', //验证码 | |||
| isSmsLogin: false, //是否手机验证码 | |||
| computeTime: 0, | |||
| } | |||
| }, | |||
| created(){ | |||
| this.getCode() | |||
| }, | |||
| methods:{ | |||
| getCode(){ | |||
| getCodeImg().then(res=>{ | |||
| this.formData.uuid = res.uuid; | |||
| this.codeUrl = "data:image/gif;base64," + res.img; | |||
| }) | |||
| }, | |||
| getSmsCode(){ | |||
| if (!this.computeTime) { | |||
| getSmsCode(this.formData.mobile).then(res =>{ | |||
| if(res.code === 200){ | |||
| Notify({ type: 'success', message: '验证码已发送' }); | |||
| this.loginForm.uuid = res.uuid; | |||
| this.computeTime = 60; | |||
| this.timer = setInterval(() => { | |||
| this.computeTime--; | |||
| if (this.computeTime <= 0) { | |||
| clearInterval(this.timer) | |||
| } | |||
| }, 1000); | |||
| } | |||
| }) | |||
| } | |||
| }, | |||
| handleLogin(values){ | |||
| if(this.isSmsLogin){ //短信登录 | |||
| }else{ //账号密码登录 | |||
| } | |||
| // this.$store.dispatch("Login",this.formData).then((res) =>{ | |||
| // console.log(res) | |||
| // }) | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| <!-- Add "scoped" attribute to limit CSS to this component only --> | |||
| <style scoped lang="scss"> | |||
| .login_page { | |||
| .focus-logo{ | |||
| width: 310px; | |||
| margin:0 auto 40px; | |||
| padding-top: 60px; | |||
| img{ | |||
| width: 100%; | |||
| } | |||
| } | |||
| .head_nav{ | |||
| li{ | |||
| width: 50%; | |||
| float: left; | |||
| height: 90px; | |||
| line-height: 90px; | |||
| text-align: center; | |||
| font-size: 30px; | |||
| background:#fff; | |||
| border-bottom: 2px solid #fff; | |||
| &.active{ | |||
| background: #F5F5F5; | |||
| border-bottom:2px solid #2386EE; | |||
| color: #2386EE; | |||
| } | |||
| } | |||
| border-bottom: 1px solid #B3BBD1; | |||
| border-top: 1px solid #eee; | |||
| } | |||
| .form_main{ | |||
| .form-submit{margin: 40px 40px 0;} | |||
| .code-img{width: 220px;} | |||
| } | |||
| } | |||
| </style> | |||