diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..61515750
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-present PanJiaChen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..61ec281f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+## 简介
+
+本项目是基于 `api工厂` 接口 和 `vue-element-admin` 开发的后台管理系统。
+
+[api工厂](https://www.it120.cc/) 是一个 PAAS 提供商,
+您可以根据工厂提供的各种api接口,开发出您自己的 SAAS 应用,非常方便。
+
+`vue-element-admin` 是一个后台集成解决方案,它基于 [Vue.js](https://github.com/vuejs/vue) 和 [element](https://github.com/ElemeFE/element)。
+
+欢迎大家一起来参与开发,踊跃提交代码,交流QQ群:315181914
+
+- [后台接口文档](http://user.api.it120.cc/swagger-ui.html)
+
+- [vue-element-admin 演示](http://panjiachen.github.io/vue-element-admin)
+
+- [vue-element-admin 使用文档](https://panjiachen.github.io/vue-element-admin-site/#/zh-cn/README)
+
+- [ICON 文档](http://iconfont.cn/)
+
+- [element-ui 文档](http://element-cn.eleme.io/#/zh-CN/component/installation)
+
+## 开发
+```bash
+# 安装依赖
+npm install
+
+# 建议不要用cnpm安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+浏览器访问 http://localhost:9527
+
+## 发布
+```bash
+# 构建测试环境
+npm run build:sit
+
+# 构建生成环境
+npm run build:prod
+```
+
\ No newline at end of file
diff --git a/build/build.js b/build/build.js
new file mode 100644
index 00000000..955e9def
--- /dev/null
+++ b/build/build.js
@@ -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,
+ 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'
+ ))
+ })
+})
diff --git a/build/check-versions.js b/build/check-versions.js
new file mode 100644
index 00000000..3ef972a0
--- /dev/null
+++ b/build/check-versions.js
@@ -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)
+ }
+}
diff --git a/build/logo.png b/build/logo.png
new file mode 100644
index 00000000..f3d2503f
Binary files /dev/null and b/build/logo.png differ
diff --git a/build/utils.js b/build/utils.js
new file mode 100644
index 00000000..e534fb0f
--- /dev/null
+++ b/build/utils.js
@@ -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')
+ })
+ }
+}
diff --git a/build/vue-loader.conf.js b/build/vue-loader.conf.js
new file mode 100644
index 00000000..33ed58bc
--- /dev/null
+++ b/build/vue-loader.conf.js
@@ -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'
+ }
+}
diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js
new file mode 100644
index 00000000..a931c276
--- /dev/null
+++ b/build/webpack.base.conf.js
@@ -0,0 +1,101 @@
+'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)
+}
+
+const createLintingRule = () => ({
+ test: /\.(js|vue)$/,
+ loader: 'eslint-loader',
+ enforce: 'pre',
+ include: [resolve('src'), resolve('test')],
+ options: {
+ formatter: require('eslint-friendly-formatter'),
+ emitWarning: !config.dev.showEslintErrorsInOverlay
+ }
+})
+
+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: [
+ ...(config.dev.useEslint ? [createLintingRule()] : []),
+ {
+ 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: /\.svg$/,
+ loader: 'svg-sprite-loader',
+ include: [resolve('src/icons')],
+ options: {
+ symbolId: 'icon-[name]'
+ }
+ },
+ {
+ test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+ loader: 'url-loader',
+ exclude: [resolve('src/icons')],
+ 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: /\.(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'
+ }
+}
diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js
new file mode 100644
index 00000000..212d0815
--- /dev/null
+++ b/build/webpack.dev.conf.js
@@ -0,0 +1,87 @@
+'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 HtmlWebpackPlugin = require('html-webpack-plugin')
+const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
+const portfinder = require('portfinder')
+
+function resolve (dir) {
+ return path.join(__dirname, '..', dir)
+}
+
+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: true,
+ hot: true,
+ 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,
+ favicon: resolve('favicon.ico'),
+ title: 'vue-element-admin'
+ }),
+ ]
+})
+
+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)
+ }
+ })
+})
diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js
new file mode 100644
index 00000000..7541838e
--- /dev/null
+++ b/build/webpack.prod.conf.js
@@ -0,0 +1,150 @@
+'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')
+
+function resolve (dir) {
+ return path.join(__dirname, '..', dir)
+}
+
+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.
+ // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
+ allChunks: false,
+ }),
+ // 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,
+ favicon: resolve('favicon.ico'),
+ title: 'vue-element-admin',
+ 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 vender 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
diff --git a/config/dev.env.js b/config/dev.env.js
new file mode 100644
index 00000000..648a19eb
--- /dev/null
+++ b/config/dev.env.js
@@ -0,0 +1,8 @@
+'use strict'
+const merge = require('webpack-merge')
+const prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+ NODE_ENV: '"development"',
+ BASE_API: '"http://user.api.it120.cc"',
+})
diff --git a/config/index.js b/config/index.js
new file mode 100644
index 00000000..5150c184
--- /dev/null
+++ b/config/index.js
@@ -0,0 +1,83 @@
+'use strict'
+// Template version: 1.2.6
+// see http://vuejs-templates.github.io/webpack for documentation.
+
+const path = require('path')
+
+module.exports = {
+ dev: {
+
+ // Paths
+ assetsSubDirectory: 'static',
+ assetsPublicPath: '/',
+ proxyTable: {},
+
+ // Various Dev Server settings
+ host: 'localhost', // can be overwritten by process.env.HOST
+ port: 9528, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+ autoOpenBrowser: true,
+ errorOverlay: true,
+ notifyOnErrors: false,
+ poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
+
+ // Use Eslint Loader?
+ // If true, your code will be linted during bundling and
+ // linting errors and warnings will be shown in the console.
+ useEslint: true,
+ // If true, eslint errors and warnings will also be shown in the error overlay
+ // in the browser.
+ showEslintErrorsInOverlay: false,
+
+ /**
+ * Source Maps
+ */
+
+ // https://webpack.js.org/configuration/devtool/#development
+ devtool: 'cheap-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,
+
+ // CSS Sourcemaps off by default because relative paths are "buggy"
+ // with this option, according to the CSS-Loader README
+ // (https://github.com/webpack/css-loader#sourcemaps)
+ // In our experience, they generally work as expected,
+ // just be aware of this issue when enabling this option.
+ cssSourceMap: false,
+ },
+
+ build: {
+ // Template for index.html
+ index: path.resolve(__dirname, '../dist/index.html'),
+
+ // Paths
+ assetsRoot: path.resolve(__dirname, '../dist'),
+ assetsSubDirectory: 'static',
+
+ // you can set by youself according to actual condition
+ assetsPublicPath: './',
+
+ /**
+ * Source Maps
+ */
+
+ productionSourceMap: false,
+ // 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
+ }
+}
diff --git a/config/prod.env.js b/config/prod.env.js
new file mode 100644
index 00000000..6908a9f0
--- /dev/null
+++ b/config/prod.env.js
@@ -0,0 +1,5 @@
+'use strict'
+module.exports = {
+ NODE_ENV: '"production"',
+ BASE_API: '"http://user.api.it120.cc"',
+}
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 00000000..34b63ac6
Binary files /dev/null and b/favicon.ico differ
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..4cdbf5f1
--- /dev/null
+++ b/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ api工厂独立后台
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..e578280b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,81 @@
+{
+ "name": "vue-admin-template",
+ "version": "3.6.0",
+ "license": "MIT",
+ "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
+ "author": "Pan ",
+ "scripts": {
+ "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
+ "start": "npm run dev",
+ "build": "node build/build.js",
+ "build:report": "npm_config_report=true node build/build.js",
+ "lint": "eslint --ext .js,.vue src",
+ "test": "npm run lint"
+ },
+ "dependencies": {
+ "axios": "0.17.1",
+ "element-ui": "2.3.8",
+ "js-cookie": "2.2.0",
+ "normalize.css": "8.0.0",
+ "nprogress": "0.2.0",
+ "qs": "6.5.2",
+ "vue": "2.5.16",
+ "vue-router": "3.0.1",
+ "vuex": "3.0.1"
+ },
+ "devDependencies": {
+ "autoprefixer": "7.2.3",
+ "babel-core": "6.26.0",
+ "babel-eslint": "8.0.3",
+ "babel-helper-vue-jsx-merge-props": "2.0.3",
+ "babel-loader": "7.1.2",
+ "babel-plugin-syntax-jsx": "6.18.0",
+ "babel-plugin-transform-runtime": "6.23.0",
+ "babel-plugin-transform-vue-jsx": "3.5.0",
+ "babel-preset-env": "1.6.1",
+ "babel-preset-stage-2": "6.24.1",
+ "chalk": "2.3.0",
+ "copy-webpack-plugin": "4.2.3",
+ "css-loader": "0.28.7",
+ "eslint": "4.13.1",
+ "eslint-friendly-formatter": "3.0.0",
+ "eslint-loader": "1.9.0",
+ "eslint-plugin-html": "4.0.1",
+ "eventsource-polyfill": "0.9.6",
+ "extract-text-webpack-plugin": "3.0.2",
+ "file-loader": "1.1.5",
+ "friendly-errors-webpack-plugin": "1.6.1",
+ "html-webpack-plugin": "2.30.1",
+ "node-notifier": "5.1.2",
+ "node-sass": "^4.7.2",
+ "optimize-css-assets-webpack-plugin": "3.2.0",
+ "ora": "1.3.0",
+ "portfinder": "1.0.13",
+ "postcss-import": "11.0.0",
+ "postcss-loader": "2.0.9",
+ "postcss-url": "7.3.0",
+ "rimraf": "2.6.2",
+ "sass-loader": "6.0.6",
+ "semver": "5.4.1",
+ "shelljs": "0.7.8",
+ "svg-sprite-loader": "3.5.2",
+ "uglifyjs-webpack-plugin": "1.1.3",
+ "url-loader": "0.6.2",
+ "vue-loader": "13.5.0",
+ "vue-style-loader": "3.0.3",
+ "vue-template-compiler": "2.5.16",
+ "webpack": "3.10.0",
+ "webpack-bundle-analyzer": "2.9.1",
+ "webpack-dev-server": "2.9.7",
+ "webpack-merge": "4.1.1"
+ },
+ "engines": {
+ "node": ">= 4.0.0",
+ "npm": ">= 3.0.0"
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 8"
+ ]
+}
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 00000000..812eef28
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/src/api/login.js b/src/api/login.js
new file mode 100644
index 00000000..b039e81c
--- /dev/null
+++ b/src/api/login.js
@@ -0,0 +1,80 @@
+import request from '@/utils/request'
+
+export function login_mobile(mobile, pwd, rememberMe, imgcode, k) {
+ return request({
+ url: '/login/mobile',
+ method: 'post',
+ data: {
+ mobile,
+ pwd,
+ rememberMe,
+ imgcode,
+ k
+ }
+ })
+}
+
+export function smscode_register(mobile, code, k) {
+ return request({
+ url: '/register/smscode',
+ method: 'post',
+ data: {
+ mobile,
+ code,
+ k
+ }
+ })
+}
+
+export function register(mobile, pwd, smsCode, name, referrer) {
+ return request({
+ url: '/register/save',
+ method: 'post',
+ data: {
+ mobile,
+ pwd,
+ smsCode,
+ name,
+ referrer
+ }
+ })
+}
+
+export function smscode_resetpwd(mobile, code, k) {
+ return request({
+ url: '/forgetPassword/smscode',
+ method: 'post',
+ data: {
+ mobile,
+ code,
+ k
+ }
+ })
+}
+
+export function resetpwd(mobile, pwd, smsCode) {
+ return request({
+ url: '/forgetPassword/resetPwd',
+ method: 'post',
+ data: {
+ mobile,
+ pwd,
+ smsCode
+ }
+ })
+}
+
+export function getInfo(token) {
+ return request({
+ url: '/user/info',
+ method: 'get',
+ params: { token }
+ })
+}
+
+export function logout() {
+ return request({
+ url: '/user/logout',
+ method: 'post'
+ })
+}
diff --git a/src/api/table.js b/src/api/table.js
new file mode 100644
index 00000000..e29c2943
--- /dev/null
+++ b/src/api/table.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getList(params) {
+ return request({
+ url: '/table/list',
+ method: 'get',
+ params
+ })
+}
diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png
new file mode 100644
index 00000000..3d8e2305
Binary files /dev/null and b/src/assets/404_images/404.png differ
diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png
new file mode 100644
index 00000000..c6281d09
Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
new file mode 100644
index 00000000..b5d94716
--- /dev/null
+++ b/src/components/Breadcrumb/index.vue
@@ -0,0 +1,51 @@
+
+
+
+
+ {{item.meta.title}}
+ {{item.meta.title}}
+
+
+
+
+
+
+
+
diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue
new file mode 100644
index 00000000..fbdf72c4
--- /dev/null
+++ b/src/components/Hamburger/index.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
new file mode 100644
index 00000000..e331a27e
--- /dev/null
+++ b/src/components/SvgIcon/index.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
diff --git a/src/icons/index.js b/src/icons/index.js
new file mode 100644
index 00000000..14e2e130
--- /dev/null
+++ b/src/icons/index.js
@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg组件
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+const req = require.context('./svg', false, /\.svg$/)
+requireAll(req)
diff --git a/src/icons/svg/example.svg b/src/icons/svg/example.svg
new file mode 100644
index 00000000..19cd9ed5
--- /dev/null
+++ b/src/icons/svg/example.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/eye.svg b/src/icons/svg/eye.svg
new file mode 100644
index 00000000..194aa45c
--- /dev/null
+++ b/src/icons/svg/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/form.svg b/src/icons/svg/form.svg
new file mode 100644
index 00000000..d848c8a8
--- /dev/null
+++ b/src/icons/svg/form.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/mobile.svg b/src/icons/svg/mobile.svg
new file mode 100644
index 00000000..917c957b
--- /dev/null
+++ b/src/icons/svg/mobile.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/password.svg b/src/icons/svg/password.svg
new file mode 100644
index 00000000..920b500b
--- /dev/null
+++ b/src/icons/svg/password.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/picture.svg b/src/icons/svg/picture.svg
new file mode 100644
index 00000000..0a4cede5
--- /dev/null
+++ b/src/icons/svg/picture.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/table.svg b/src/icons/svg/table.svg
new file mode 100644
index 00000000..da6ffffa
--- /dev/null
+++ b/src/icons/svg/table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/tree.svg b/src/icons/svg/tree.svg
new file mode 100644
index 00000000..11cedc04
--- /dev/null
+++ b/src/icons/svg/tree.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/user.svg b/src/icons/svg/user.svg
new file mode 100644
index 00000000..5971deeb
--- /dev/null
+++ b/src/icons/svg/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 00000000..635cc8d6
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,28 @@
+import Vue from 'vue'
+
+import 'normalize.css/normalize.css'// A modern alternative to CSS resets
+
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import locale from 'element-ui/lib/locale/lang/en' // lang i18n
+
+import '@/styles/index.scss' // global css
+
+import App from './App'
+import router from './router'
+import store from './store'
+
+import '@/icons' // icon
+import '@/permission' // permission control
+
+Vue.use(ElementUI, { locale })
+
+Vue.config.productionTip = false
+
+new Vue({
+ el: '#app',
+ router,
+ store,
+ template: '',
+ components: { App }
+})
diff --git a/src/permission.js b/src/permission.js
new file mode 100644
index 00000000..f0832a81
--- /dev/null
+++ b/src/permission.js
@@ -0,0 +1,41 @@
+import router from './router'
+import store from './store'
+import NProgress from 'nprogress' // Progress 进度条
+import 'nprogress/nprogress.css'// Progress 进度条样式
+import { Message } from 'element-ui'
+import { getToken } from '@/utils/auth' // 验权
+
+const whiteList = ['/login', '/register', '/resetpwd'] // 不重定向白名单
+router.beforeEach((to, from, next) => {
+ NProgress.start()
+ if (getToken()) {
+ if (to.path === '/login') {
+ next({ path: '/' })
+ NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
+ } else {
+ if (store.getters.roles.length === 0) {
+ store.dispatch('GetInfo').then(res => { // 拉取用户信息
+ next()
+ }).catch((err) => {
+ store.dispatch('FedLogOut').then(() => {
+ Message.error(err || 'Verification failed, please login again')
+ next({ path: '/' })
+ })
+ })
+ } else {
+ next()
+ }
+ }
+ } else {
+ if (whiteList.indexOf(to.path) !== -1) {
+ next()
+ } else {
+ next('/login')
+ NProgress.done()
+ }
+ }
+})
+
+router.afterEach(() => {
+ NProgress.done() // 结束Progress
+})
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 00000000..88fb0f44
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,85 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
+// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
+
+Vue.use(Router)
+
+/* Layout */
+import Layout from '../views/layout/Layout'
+
+/**
+* hidden: true if `hidden:true` will not show in the sidebar(default is false)
+* alwaysShow: true if set true, will always show the root menu, whatever its child routes length
+* if not set alwaysShow, only more than one route under the children
+* it will becomes nested mode, otherwise not show the root menu
+* redirect: noredirect if `redirect:noredirect` will no redirct in the breadcrumb
+* name:'router-name' the name is used by (must set!!!)
+* meta : {
+ title: 'title' the name show in submenu and breadcrumb (recommend set)
+ icon: 'svg-name' the icon show in the sidebar,
+ }
+**/
+export const constantRouterMap = [
+ { path: '/login', component: () => import('@/views/login/index'), hidden: true },
+ { path: '/register', component: () => import('@/views/login/register'), hidden: true },
+ { path: '/resetpwd', component: () => import('@/views/login/resetpwd'), hidden: true },
+ { path: '/404', component: () => import('@/views/404'), hidden: true },
+
+ {
+ path: '/',
+ component: Layout,
+ redirect: '/dashboard',
+ name: 'Dashboard',
+ hidden: true,
+ children: [{
+ path: 'dashboard',
+ component: () => import('@/views/dashboard/index')
+ }]
+ },
+
+ {
+ path: '/example',
+ component: Layout,
+ redirect: '/example/table',
+ name: 'Example',
+ meta: { title: 'Example', icon: 'example' },
+ children: [
+ {
+ path: 'table',
+ name: 'Table',
+ component: () => import('@/views/table/index'),
+ meta: { title: 'Table', icon: 'table' }
+ },
+ {
+ path: 'tree',
+ name: 'Tree',
+ component: () => import('@/views/tree/index'),
+ meta: { title: 'Tree', icon: 'tree' }
+ }
+ ]
+ },
+
+ {
+ path: '/form',
+ component: Layout,
+ children: [
+ {
+ path: 'index',
+ name: 'Form',
+ component: () => import('@/views/form/index'),
+ meta: { title: 'Form', icon: 'form' }
+ }
+ ]
+ },
+
+ { path: '*', redirect: '/404', hidden: true }
+]
+
+export default new Router({
+ // mode: 'history', //后端支持可开
+ scrollBehavior: () => ({ y: 0 }),
+ routes: constantRouterMap
+})
+
diff --git a/src/store/getters.js b/src/store/getters.js
new file mode 100644
index 00000000..7fbf1f4f
--- /dev/null
+++ b/src/store/getters.js
@@ -0,0 +1,9 @@
+const getters = {
+ sidebar: state => state.app.sidebar,
+ device: state => state.app.device,
+ token: state => state.user.token,
+ avatar: state => state.user.avatar,
+ name: state => state.user.name,
+ roles: state => state.user.roles
+}
+export default getters
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 00000000..6b6be08e
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,17 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import app from './modules/app'
+import user from './modules/user'
+import getters from './getters'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+ modules: {
+ app,
+ user
+ },
+ getters
+})
+
+export default store
diff --git a/src/store/modules/app.js b/src/store/modules/app.js
new file mode 100644
index 00000000..f4872415
--- /dev/null
+++ b/src/store/modules/app.js
@@ -0,0 +1,43 @@
+import Cookies from 'js-cookie'
+
+const app = {
+ state: {
+ sidebar: {
+ opened: !+Cookies.get('sidebarStatus'),
+ withoutAnimation: false
+ },
+ device: 'desktop'
+ },
+ mutations: {
+ TOGGLE_SIDEBAR: state => {
+ if (state.sidebar.opened) {
+ Cookies.set('sidebarStatus', 1)
+ } else {
+ Cookies.set('sidebarStatus', 0)
+ }
+ state.sidebar.opened = !state.sidebar.opened
+ state.sidebar.withoutAnimation = false
+ },
+ CLOSE_SIDEBAR: (state, withoutAnimation) => {
+ Cookies.set('sidebarStatus', 1)
+ state.sidebar.opened = false
+ state.sidebar.withoutAnimation = withoutAnimation
+ },
+ TOGGLE_DEVICE: (state, device) => {
+ state.device = device
+ }
+ },
+ actions: {
+ ToggleSideBar: ({ commit }) => {
+ commit('TOGGLE_SIDEBAR')
+ },
+ CloseSideBar({ commit }, { withoutAnimation }) {
+ commit('CLOSE_SIDEBAR', withoutAnimation)
+ },
+ ToggleDevice({ commit }, device) {
+ commit('TOGGLE_DEVICE', device)
+ }
+ }
+}
+
+export default app
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
new file mode 100644
index 00000000..b504067a
--- /dev/null
+++ b/src/store/modules/user.js
@@ -0,0 +1,87 @@
+import { login, logout, getInfo } from '@/api/login'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+
+const user = {
+ state: {
+ token: getToken(),
+ name: '',
+ avatar: '',
+ roles: []
+ },
+
+ 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
+ }
+ },
+
+ actions: {
+ // 登录
+ Login({ commit }, userInfo) {
+ const username = userInfo.username.trim()
+ return new Promise((resolve, reject) => {
+ login(username, userInfo.password).then(response => {
+ const data = response.data
+ setToken(data.token)
+ commit('SET_TOKEN', data.token)
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // 获取用户信息
+ GetInfo({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ getInfo(state.token).then(response => {
+ const data = response.data
+ if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
+ commit('SET_ROLES', data.roles)
+ } else {
+ reject('getInfo: roles must be a non-null array !')
+ }
+ commit('SET_NAME', data.name)
+ commit('SET_AVATAR', data.avatar)
+ resolve(response)
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // 登出
+ LogOut({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ logout(state.token).then(() => {
+ commit('SET_TOKEN', '')
+ commit('SET_ROLES', [])
+ removeToken()
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // 前端 登出
+ FedLogOut({ commit }) {
+ return new Promise(resolve => {
+ commit('SET_TOKEN', '')
+ removeToken()
+ resolve()
+ })
+ }
+ }
+}
+
+export default user
diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss
new file mode 100644
index 00000000..ef7bb5da
--- /dev/null
+++ b/src/styles/element-ui.scss
@@ -0,0 +1,29 @@
+ //to reset element-ui default css
+.el-upload {
+ input[type="file"] {
+ display: none !important;
+ }
+}
+
+.el-upload__input {
+ display: none;
+}
+
+//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+ transform: none;
+ left: 0;
+ position: relative;
+ margin: 0 auto;
+}
+
+//element ui upload
+.upload-container {
+ .el-upload {
+ width: 100%;
+ .el-upload-dragger {
+ width: 100%;
+ height: 200px;
+ }
+ }
+}
diff --git a/src/styles/index.scss b/src/styles/index.scss
new file mode 100644
index 00000000..d395d84e
--- /dev/null
+++ b/src/styles/index.scss
@@ -0,0 +1,78 @@
+@import './variables.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+
+body {
+ height: 100%;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ text-rendering: optimizeLegibility;
+ font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+ font-weight: 700;
+}
+
+html {
+ height: 100%;
+ box-sizing: border-box;
+}
+
+#app{
+ height: 100%;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+a,
+a:focus,
+a:hover {
+ cursor: pointer;
+ color: inherit;
+ outline: none;
+ text-decoration: none;
+}
+
+div:focus{
+ outline: none;
+ }
+
+a:focus,
+a:active {
+ outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+ cursor: pointer;
+ color: inherit;
+ text-decoration: none;
+}
+
+.clearfix {
+ &:after {
+ visibility: hidden;
+ display: block;
+ font-size: 0;
+ content: " ";
+ clear: both;
+ height: 0;
+ }
+}
+
+//main-container全局样式
+.app-main{
+ min-height: 100%
+}
+
+.app-container {
+ padding: 20px;
+}
diff --git a/src/styles/mixin.scss b/src/styles/mixin.scss
new file mode 100644
index 00000000..601d7a03
--- /dev/null
+++ b/src/styles/mixin.scss
@@ -0,0 +1,27 @@
+@mixin clearfix {
+ &:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+}
+
+@mixin scrollBar {
+ &::-webkit-scrollbar-track-piece {
+ background: #d3dce6;
+ }
+ &::-webkit-scrollbar {
+ width: 6px;
+ }
+ &::-webkit-scrollbar-thumb {
+ background: #99a9bf;
+ border-radius: 20px;
+ }
+}
+
+@mixin relative {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss
new file mode 100644
index 00000000..26cece0c
--- /dev/null
+++ b/src/styles/sidebar.scss
@@ -0,0 +1,118 @@
+#app {
+ // 主体区域
+ .main-container {
+ min-height: 100%;
+ transition: margin-left .28s;
+ margin-left: 180px;
+ }
+ // 侧边栏
+ .sidebar-container {
+ transition: width 0.28s;
+ width: 180px !important;
+ height: 100%;
+ position: fixed;
+ font-size: 0px;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1001;
+ overflow: hidden;
+ //reset element-ui css
+ .horizontal-collapse-transition {
+ transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+ }
+ .scrollbar-wrapper {
+ height: calc(100% + 15px);
+ .el-scrollbar__view {
+ height: 100%;
+ }
+ }
+ .is-horizontal {
+ display: none;
+ }
+ a {
+ display: inline-block;
+ width: 100%;
+ overflow: hidden;
+ }
+ .svg-icon {
+ margin-right: 16px;
+ }
+ .el-menu {
+ border: none;
+ height: 100%;
+ width: 100% !important;
+ }
+ }
+ .hideSidebar {
+ .sidebar-container {
+ width: 36px !important;
+ }
+ .main-container {
+ margin-left: 36px;
+ }
+ .submenu-title-noDropdown {
+ padding-left: 10px !important;
+ position: relative;
+ .el-tooltip {
+ padding: 0 10px !important;
+ }
+ }
+ .el-submenu {
+ overflow: hidden;
+ &>.el-submenu__title {
+ padding-left: 10px !important;
+ .el-submenu__icon-arrow {
+ display: none;
+ }
+ }
+ }
+ .el-menu--collapse {
+ .el-submenu {
+ &>.el-submenu__title {
+ &>span {
+ height: 0;
+ width: 0;
+ overflow: hidden;
+ visibility: hidden;
+ display: inline-block;
+ }
+ }
+ }
+ }
+ }
+ .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
+ .sidebar-container .el-submenu .el-menu-item {
+ min-width: 180px !important;
+ background-color: $subMenuBg !important;
+ &:hover {
+ background-color: $menuHover !important;
+ }
+ }
+ .el-menu--collapse .el-menu .el-submenu {
+ min-width: 180px !important;
+ }
+
+ //适配移动端
+ .mobile {
+ .main-container {
+ margin-left: 0px;
+ }
+ .sidebar-container {
+ transition: transform .28s;
+ width: 180px !important;
+ }
+ &.hideSidebar {
+ .sidebar-container {
+ transition-duration: 0.3s;
+ transform: translate3d(-180px, 0, 0);
+ }
+ }
+ }
+ .withoutAnimation {
+ .main-container,
+ .sidebar-container {
+ transition: none;
+ }
+ }
+}
diff --git a/src/styles/transition.scss b/src/styles/transition.scss
new file mode 100644
index 00000000..c4d47ad0
--- /dev/null
+++ b/src/styles/transition.scss
@@ -0,0 +1,32 @@
+//globl transition css
+
+/*fade*/
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+ opacity: 0;
+}
+
+/*fade*/
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+ transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+ opacity: 0;
+ transform: translateX(20px);
+}
+
+.breadcrumb-move {
+ transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+ position: absolute;
+}
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
new file mode 100644
index 00000000..2fee827b
--- /dev/null
+++ b/src/styles/variables.scss
@@ -0,0 +1,4 @@
+//sidebar
+$menuBg:#304156;
+$subMenuBg:#1f2d3d;
+$menuHover:#001528;
diff --git a/src/utils/auth.js b/src/utils/auth.js
new file mode 100644
index 00000000..08a43d6e
--- /dev/null
+++ b/src/utils/auth.js
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'Admin-Token'
+
+export function getToken() {
+ return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+ return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+ return Cookies.remove(TokenKey)
+}
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 00000000..9657c9ce
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,58 @@
+/**
+ * Created by jiachenpan on 16/11/18.
+ */
+
+export function parseTime(time, cFormat) {
+ if (arguments.length === 0) {
+ return null
+ }
+ const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+ let date
+ if (typeof time === 'object') {
+ date = time
+ } else {
+ if (('' + time).length === 10) time = parseInt(time) * 1000
+ date = new Date(time)
+ }
+ const formatObj = {
+ y: date.getFullYear(),
+ m: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ i: date.getMinutes(),
+ s: date.getSeconds(),
+ a: date.getDay()
+ }
+ const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+ let value = formatObj[key]
+ if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
+ if (result.length > 0 && value < 10) {
+ value = '0' + value
+ }
+ return value || 0
+ })
+ return time_str
+}
+
+export function formatTime(time, option) {
+ time = +time * 1000
+ const d = new Date(time)
+ const now = Date.now()
+
+ const diff = (now - d) / 1000
+
+ if (diff < 30) {
+ return '刚刚'
+ } else if (diff < 3600) { // less 1 hour
+ return Math.ceil(diff / 60) + '分钟前'
+ } else if (diff < 3600 * 24) {
+ return Math.ceil(diff / 3600) + '小时前'
+ } else if (diff < 3600 * 24 * 2) {
+ return '1天前'
+ }
+ if (option) {
+ return parseTime(time, option)
+ } else {
+ return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
+ }
+}
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 00000000..7051cb1c
--- /dev/null
+++ b/src/utils/request.js
@@ -0,0 +1,72 @@
+import axios from 'axios'
+import qs from 'qs'
+import { Message, MessageBox } from 'element-ui'
+import store from '../store'
+import { getToken, removeToken } from '@/utils/auth'
+
+// 创建axios实例
+const service = axios.create({
+ baseURL: process.env.BASE_API, // api的base_url
+ timeout: 15000 // 请求超时时间
+})
+
+// request拦截器
+service.interceptors.request.use(config => {
+ config.headers['X-Token'] = getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
+ //config.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
+ config.data = qs.stringify(config.data);//将对象解析成URL的形式
+ return config
+}, error => {
+ // Do something with request error
+ console.log(error) // for debug
+ Promise.reject(error)
+})
+
+// respone拦截器
+service.interceptors.response.use(
+ response => {
+ /**
+ * code为非20000是抛错 可结合自己业务进行修改
+ */
+ const res = response.data
+ if (res.code === 100000 || res.code === 100001) {
+ MessageBox.confirm('登录超时,请重新登录', '确定登出', {
+ confirmButtonText: '重新登录',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }).then(() => {
+ removeToken()
+ location.reload()
+ })
+ return Promise.reject('登录超时,请重新登录')
+ }
+ if (res.code === 100002) {
+ Message({
+ message: '抱歉,您无权操作',
+ type: 'error',
+ duration: 3 * 1000
+ })
+ return Promise.reject('抱歉,您无权操作')
+ }
+ if (res.code === 100003) {
+ Message({
+ message: '请先绑定代理商',
+ type: 'error',
+ duration: 3 * 1000
+ })
+ return Promise.reject('请先绑定代理商')
+ }
+ return response.data
+ },
+ error => {
+ console.log('err' + error)// for debug
+ Message({
+ message: error.message,
+ type: 'error',
+ duration: 5 * 1000
+ })
+ return Promise.reject(error)
+ }
+)
+
+export default service
diff --git a/src/utils/validate.js b/src/utils/validate.js
new file mode 100644
index 00000000..834a8dd9
--- /dev/null
+++ b/src/utils/validate.js
@@ -0,0 +1,33 @@
+/**
+ * Created by jiachenpan on 16/11/18.
+ */
+
+export function isvalidUsername(str) {
+ const valid_map = ['admin', 'editor']
+ return valid_map.indexOf(str.trim()) >= 0
+}
+
+/* 合法uri*/
+export function validateURL(textval) {
+ const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
+ return urlregex.test(textval)
+}
+
+/* 小写字母*/
+export function validateLowerCase(str) {
+ const reg = /^[a-z]+$/
+ return reg.test(str)
+}
+
+/* 大写字母*/
+export function validateUpperCase(str) {
+ const reg = /^[A-Z]+$/
+ return reg.test(str)
+}
+
+/* 大小写字母*/
+export function validatAlphabets(str) {
+ const reg = /^[A-Za-z]+$/
+ return reg.test(str)
+}
+
diff --git a/src/views/404.vue b/src/views/404.vue
new file mode 100644
index 00000000..ca160fa5
--- /dev/null
+++ b/src/views/404.vue
@@ -0,0 +1,229 @@
+
+
+
+
+
+
OOPS!
+
+
{{ message }}
+
请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告
+
返回首页
+
+
+
+
+
+
+
+
diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue
new file mode 100644
index 00000000..4b4625c7
--- /dev/null
+++ b/src/views/dashboard/index.vue
@@ -0,0 +1,32 @@
+
+
+
name:{{name}}
+
roles:{{role}}
+
+
+
+
+
+
diff --git a/src/views/form/index.vue b/src/views/form/index.vue
new file mode 100644
index 00000000..bfec9a28
--- /dev/null
+++ b/src/views/form/index.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Cancel
+
+
+
+
+
+
+
+
+
diff --git a/src/views/layout/Layout.vue b/src/views/layout/Layout.vue
new file mode 100644
index 00000000..c2c906eb
--- /dev/null
+++ b/src/views/layout/Layout.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
diff --git a/src/views/layout/components/AppMain.vue b/src/views/layout/components/AppMain.vue
new file mode 100644
index 00000000..4babff50
--- /dev/null
+++ b/src/views/layout/components/AppMain.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/src/views/layout/components/Navbar.vue b/src/views/layout/components/Navbar.vue
new file mode 100644
index 00000000..eb36b1ac
--- /dev/null
+++ b/src/views/layout/components/Navbar.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+ Home
+
+
+
+ LogOut
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/layout/components/Sidebar/SidebarItem.vue b/src/views/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 00000000..aa719d19
--- /dev/null
+++ b/src/views/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
diff --git a/src/views/layout/components/Sidebar/index.vue b/src/views/layout/components/Sidebar/index.vue
new file mode 100644
index 00000000..ed2948b1
--- /dev/null
+++ b/src/views/layout/components/Sidebar/index.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/layout/components/index.js b/src/views/layout/components/index.js
new file mode 100644
index 00000000..97ee3cd1
--- /dev/null
+++ b/src/views/layout/components/index.js
@@ -0,0 +1,3 @@
+export { default as Navbar } from './Navbar'
+export { default as Sidebar } from './Sidebar'
+export { default as AppMain } from './AppMain'
diff --git a/src/views/layout/mixin/ResizeHandler.js b/src/views/layout/mixin/ResizeHandler.js
new file mode 100644
index 00000000..b22c8bb6
--- /dev/null
+++ b/src/views/layout/mixin/ResizeHandler.js
@@ -0,0 +1,41 @@
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 1024
+const RATIO = 3
+
+export default {
+ watch: {
+ $route(route) {
+ if (this.device === 'mobile' && this.sidebar.opened) {
+ store.dispatch('CloseSideBar', { withoutAnimation: false })
+ }
+ }
+ },
+ beforeMount() {
+ window.addEventListener('resize', this.resizeHandler)
+ },
+ mounted() {
+ const isMobile = this.isMobile()
+ if (isMobile) {
+ store.dispatch('ToggleDevice', 'mobile')
+ store.dispatch('CloseSideBar', { withoutAnimation: true })
+ }
+ },
+ methods: {
+ isMobile() {
+ const rect = body.getBoundingClientRect()
+ return rect.width - RATIO < WIDTH
+ },
+ resizeHandler() {
+ if (!document.hidden) {
+ const isMobile = this.isMobile()
+ store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
+
+ if (isMobile) {
+ store.dispatch('CloseSideBar', { withoutAnimation: true })
+ }
+ }
+ }
+ }
+}
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
new file mode 100644
index 00000000..88532229
--- /dev/null
+++ b/src/views/login/index.vue
@@ -0,0 +1,253 @@
+
+
+
+ api工厂独立后台
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+
+
+ 开通新后台
+
+
+ 忘记密码?
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/login/register.vue b/src/views/login/register.vue
new file mode 100644
index 00000000..d2c76915
--- /dev/null
+++ b/src/views/login/register.vue
@@ -0,0 +1,288 @@
+
+
+
+ 注册开通新后台账号
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 获取验证码
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+
+
+ 现有账号登陆
+
+
+ 忘记密码?
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/login/resetpwd.vue b/src/views/login/resetpwd.vue
new file mode 100644
index 00000000..0f15fabd
--- /dev/null
+++ b/src/views/login/resetpwd.vue
@@ -0,0 +1,282 @@
+
+
+
+ 找回(重置)登录密码
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 获取验证码
+
+
+
+
+
+
+
+
+
+
+ 登录
+
+
+
+
+ 现有账号登陆
+
+
+ 开通新后台
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/table/index.vue b/src/views/table/index.vue
new file mode 100644
index 00000000..899c3418
--- /dev/null
+++ b/src/views/table/index.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+ {{scope.$index}}
+
+
+
+
+ {{scope.row.title}}
+
+
+
+
+ {{scope.row.author}}
+
+
+
+
+ {{scope.row.pageviews}}
+
+
+
+
+ {{scope.row.status}}
+
+
+
+
+
+ {{scope.row.display_time}}
+
+
+
+
+
+
+
diff --git a/src/views/tree/index.vue b/src/views/tree/index.vue
new file mode 100644
index 00000000..2bfa6967
--- /dev/null
+++ b/src/views/tree/index.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/static/.gitkeep b/static/.gitkeep
new file mode 100644
index 00000000..e69de29b