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