| @@ -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> | |||||