├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── Dockerfile ├── LICENSE ├── README.md ├── build ├── build.js ├── check-versions.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── default.conf ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── assets │ ├── images │ │ ├── bottom-left.png │ │ └── bottom-right.png │ └── styles │ │ ├── home.css │ │ ├── icon-font │ │ ├── iconfont.eot │ │ ├── iconfont.svg │ │ ├── iconfont.ttf │ │ └── iconfont.woff │ │ ├── iconfont.css │ │ ├── reports.css │ │ ├── swagger.css │ │ └── tree.css ├── main.js ├── pages │ ├── auth │ │ ├── Login.vue │ │ ├── Register.vue │ │ └── components │ │ │ ├── Login.vue │ │ │ └── Register.vue │ ├── config │ │ ├── RecordConfig.vue │ │ └── components │ │ │ ├── ConfigBody.vue │ │ │ └── ConfigList.vue │ ├── fastrunner │ │ ├── api │ │ │ ├── RecordApi.vue │ │ │ └── components │ │ │ │ ├── ApiBody.vue │ │ │ │ └── ApiList.vue │ │ ├── case │ │ │ ├── AutoTest.vue │ │ │ └── components │ │ │ │ ├── EditTest.vue │ │ │ │ ├── TestBody.vue │ │ │ │ └── TestList.vue │ │ └── config │ │ │ ├── RecordConfig.vue │ │ │ └── components │ │ │ ├── ConfigBody.vue │ │ │ └── ConfigList.vue │ ├── home │ │ ├── Home.vue │ │ └── components │ │ │ ├── Header.vue │ │ │ └── Side.vue │ ├── httprunner │ │ ├── DebugTalk.vue │ │ └── components │ │ │ ├── Extract.vue │ │ │ ├── Headers.vue │ │ │ ├── Hooks.vue │ │ │ ├── Parameters.vue │ │ │ ├── Request.vue │ │ │ ├── Validate.vue │ │ │ └── Variables.vue │ ├── project │ │ ├── DataBase.vue │ │ ├── ProjectDetail.vue │ │ └── ProjectList.vue │ ├── reports │ │ ├── DebugReport.vue │ │ └── ReportList.vue │ ├── task │ │ ├── AddTasks.vue │ │ └── Tasks.vue │ └── variables │ │ ├── GlobalEnv.vue │ │ └── HostAddress.vue ├── restful │ └── api.js ├── router │ └── index.js └── store │ ├── index.js │ ├── mutations.js │ └── state.js └── static ├── .gitkeep └── favicon.ico /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM hub.c.163.com/library/nginx 2 | 3 | MAINTAINER yinquanwang 4 | 5 | RUN rm /etc/nginx/conf.d/default.conf 6 | 7 | RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone 8 | 9 | ADD default.conf /etc/nginx/conf.d/ 10 | 11 | COPY dist/ /usr/share/nginx/html/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 yinquanwang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FasterWeb 2 | 3 | ![LICENSE](https://img.shields.io/github/license/yinquanwang/FasterRunner.svg) 4 | > FasterWeb that depends FasterRunner 5 | 6 | ## 本地开发环境部署 7 | 8 | ``` bash 9 | # install dependencies 10 | npm install 11 | 12 | # serve with hot reload at localhost:8080 13 | npm run dev 14 | 15 | ``` 16 | 17 | 测试 18 | ----------- 19 | 20 | 1. open url(recommend chrome): http://localhost:8080/fastrunner/register 21 | 22 | ## Docker 部署 nginx模式 23 | -------------- 24 | 1. 修改default.conf配置文件 server_name的ip(宿主机IP), 端口默认8080 25 | 2. 修改/src/restful/api.js baseUrl地址, 即为fastrunner容器运行的宿主机地址 26 | 3. 执行npm install, npm run build # 生成生产环境包 27 | 3. docker build -t fasterweb:latest . # 构建docker镜像 28 | 4. docker run -d --name fasterweb --net=host --restart always fasterweb:latest # 后台运行docker容器 29 | 5. open url: http://宿主机ip:8080/fastrunner/register 30 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve(dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | 12 | module.exports = { 13 | context: path.resolve(__dirname, '../'), 14 | entry: { 15 | app: './src/main.js' 16 | }, 17 | output: { 18 | path: config.build.assetsRoot, 19 | filename: '[name].js', 20 | publicPath: process.env.NODE_ENV === 'production' 21 | ? config.build.assetsPublicPath 22 | : config.dev.assetsPublicPath 23 | }, 24 | resolve: { 25 | extensions: ['.js', '.vue', '.json'], 26 | alias: { 27 | 'vue$': 'vue/dist/vue.esm.js', 28 | '@': resolve('src'), 29 | 'styles': resolve('src/assets/styles'), 30 | } 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.vue$/, 36 | loader: 'vue-loader', 37 | options: vueLoaderConfig 38 | }, 39 | { 40 | test: /\.js$/, 41 | loader: 'babel-loader', 42 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 43 | }, 44 | { 45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 46 | loader: 'url-loader', 47 | options: { 48 | limit: 10000, 49 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 50 | } 51 | }, 52 | { 53 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 54 | loader: 'url-loader', 55 | options: { 56 | limit: 10000, 57 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 58 | } 59 | }, 60 | { 61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 62 | loader: 'url-loader', 63 | options: { 64 | limit: 10000, 65 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 66 | } 67 | } 68 | ] 69 | }, 70 | node: { 71 | // prevent webpack from injecting useless setImmediate polyfill because Vue 72 | // source contains it (although only uses it if it's native). 73 | setImmediate: false, 74 | // prevent webpack from injecting mocks to Node native modules 75 | // that does not make sense for the client 76 | dgram: 'empty', 77 | fs: 'empty', 78 | net: 'empty', 79 | tls: 'empty', 80 | child_process: 'empty' 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? { warnings: false, errors: true } 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/dev.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }) 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err) 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })) 91 | 92 | resolve(devWebpackConfig) 93 | } 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | }) 8 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | /* '/httprunner': { 15 | target: 'http://localhost:8000',//设置你调用的接口域名和端口号 别忘了加http 16 | changeOrigin: true, 17 | pathRewrite: { 18 | '^/httprunner': '' 19 | } 20 | }*/ 21 | }, 22 | 23 | // Various Dev Server settings 24 | host: 'localhost', // can be overwritten by process.variables.HOST 25 | port: 8080, // can be overwritten by process.variables.PORT, if port is in use, a free one will be determined 26 | autoOpenBrowser: false, 27 | errorOverlay: true, 28 | notifyOnErrors: true, 29 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 30 | 31 | 32 | /** 33 | * Source Maps 34 | */ 35 | 36 | // https://webpack.js.org/configuration/devtool/#development 37 | devtool: 'cheap-module-eval-source-map', 38 | 39 | // If you have problems debugging vue-files in devtools, 40 | // set this to false - it *may* help 41 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 42 | cacheBusting: true, 43 | 44 | cssSourceMap: true 45 | }, 46 | 47 | build: { 48 | // Template for index.html 49 | index: path.resolve(__dirname, '../dist/index.html'), 50 | 51 | // Paths 52 | assetsRoot: path.resolve(__dirname, '../dist'), 53 | assetsSubDirectory: 'static', 54 | assetsPublicPath: '/', 55 | 56 | /** 57 | * Source Maps 58 | */ 59 | 60 | productionSourceMap: true, 61 | // https://webpack.js.org/configuration/devtool/#production 62 | devtool: '#source-map', 63 | 64 | // Gzip off by default as many popular static hosts such as 65 | // Surge or Netlify already gzip all static assets for you. 66 | // Before setting to `true`, make sure to: 67 | // npm install --save-dev compression-webpack-plugin 68 | productionGzip: false, 69 | productionGzipExtensions: ['js', 'css'], 70 | 71 | // Run the build command with an extra argument to 72 | // View the bundle analyzer report after build finishes: 73 | // `npm run build --report` 74 | // Set to `true` or `false` to always turn it on or off 75 | bundleAnalyzerReport: process.env.npm_config_report 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | } 5 | -------------------------------------------------------------------------------- /default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name localhost; # 修改为docker服务宿主机的ip 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html =404; 9 | } 10 | 11 | error_page 500 502 503 504 /50x.html; 12 | location = /50x.html { 13 | root html; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FastRunner 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-web", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "尹全旺 <1263374981@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.18.0", 14 | "element-ui": "^2.4.6", 15 | "vue": "^2.5.2", 16 | "vue-monaco-editor": "0.0.19", 17 | "vue-router": "^3.0.1", 18 | "vuedraggable": "^2.16.0", 19 | "vuex": "^3.0.1" 20 | }, 21 | "devDependencies": { 22 | "autoprefixer": "^7.1.2", 23 | "babel-core": "^6.22.1", 24 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 25 | "babel-loader": "^7.1.1", 26 | "babel-plugin-syntax-jsx": "^6.18.0", 27 | "babel-plugin-transform-runtime": "^6.22.0", 28 | "babel-plugin-transform-vue-jsx": "^3.5.0", 29 | "babel-preset-env": "^1.3.2", 30 | "babel-preset-stage-2": "^6.22.0", 31 | "chalk": "^2.0.1", 32 | "copy-webpack-plugin": "^4.0.1", 33 | "css-loader": "^0.28.0", 34 | "extract-text-webpack-plugin": "^3.0.0", 35 | "file-loader": "^1.1.4", 36 | "friendly-errors-webpack-plugin": "^1.6.1", 37 | "html-webpack-plugin": "^2.30.1", 38 | "node-notifier": "^5.1.2", 39 | "optimize-css-assets-webpack-plugin": "^3.2.0", 40 | "ora": "^1.2.0", 41 | "portfinder": "^1.0.13", 42 | "postcss-import": "^11.0.0", 43 | "postcss-loader": "^2.0.8", 44 | "postcss-url": "^7.2.1", 45 | "rimraf": "^2.6.0", 46 | "semver": "^5.3.0", 47 | "shelljs": "^0.7.6", 48 | "uglifyjs-webpack-plugin": "^1.1.1", 49 | "url-loader": "^0.5.8", 50 | "vue-easytable": "^1.7.1", 51 | "vue-loader": "^13.3.0", 52 | "vue-style-loader": "^3.0.1", 53 | "vue-template-compiler": "^2.5.2", 54 | "vue2-ace-editor": "0.0.11", 55 | "webpack": "^3.6.0", 56 | "webpack-bundle-analyzer": "^2.9.0", 57 | "webpack-dev-server": "^2.9.1", 58 | "webpack-merge": "^4.1.0" 59 | }, 60 | "engines": { 61 | "node": ">= 6.0.0", 62 | "npm": ">= 3.0.0" 63 | }, 64 | "browserslist": [ 65 | "> 1%", 66 | "last 2 versions", 67 | "not ie <= 8" 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 29 | -------------------------------------------------------------------------------- /src/assets/images/bottom-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/FasterWeb/10b5c2471802c52cefd478c237c884fc36ba1f95/src/assets/images/bottom-left.png -------------------------------------------------------------------------------- /src/assets/images/bottom-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/FasterWeb/10b5c2471802c52cefd478c237c884fc36ba1f95/src/assets/images/bottom-right.png -------------------------------------------------------------------------------- /src/assets/styles/home.css: -------------------------------------------------------------------------------- 1 | #form-content { 2 | width: 450px; 3 | margin: 40px auto; 4 | background-color: #FFFFFF; 5 | padding: 40px 50px; 6 | text-align: center; 7 | border-radius: 2px; 8 | -webkit-box-shadow: 0px 16px 44px 0 RGBA(0, 0, 1, 0.2); 9 | -moz-box-shadow: 0px 16px 44px 0 RGBA(0, 0, 1, 0.2); 10 | box-shadow: 0px 16px 44px 0 RGBA(0, 0, 1, 0.2); 11 | } 12 | 13 | .bottom-right { 14 | position: fixed; 15 | right: 0px; 16 | bottom: 0px; 17 | } 18 | 19 | .bottom-left { 20 | position: fixed; 21 | left: 0px; 22 | bottom: 0px; 23 | } 24 | 25 | .bottom-right img { 26 | width: 364px; 27 | height: 208px; 28 | } 29 | 30 | .bottom-left img { 31 | width: 332px; 32 | height: 271px; 33 | } 34 | 35 | img { 36 | vertical-align: middle; 37 | } 38 | 39 | img { 40 | border: 0; 41 | } 42 | 43 | .form-input-div .icon { 44 | position: relative; 45 | left: 16px; 46 | top: 8px; 47 | color: #172B4D; 48 | font-size: 18px; 49 | } 50 | 51 | .form-input-div input.has-error { 52 | border-color: #fc4949; 53 | } 54 | 55 | #form-msg { 56 | color: #172B4D; 57 | font-size: 30px; 58 | text-align: left; 59 | font-weight: 300; 60 | } 61 | 62 | .form-submit .btn-primary { 63 | width: 100%; 64 | height: 42px; 65 | font-size: 15px; 66 | color: #fff; 67 | background: #1F5DEA; 68 | border: 1px solid #1F5DEA; 69 | border-radius: 2px; 70 | } 71 | 72 | .form-foot { 73 | position: relative; 74 | margin-top: 20px; 75 | color: #A8ACB9; 76 | font-size: 12px; 77 | text-align: left; 78 | } 79 | 80 | .form-foot a, .form-foot a:hover, .form-foot a:active { 81 | color: #1F5DEA; 82 | text-decoration: none; 83 | } 84 | 85 | body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, button, textarea, p, blockquote, th, td { 86 | margin: 0; 87 | padding: 0; 88 | -webkit-box-sizing: border-box; 89 | -moz-box-sizing: border-box; 90 | box-sizing: border-box; 91 | } 92 | 93 | a:hover, a:active, a:visited, a:focus { 94 | text-decoration: none !important; 95 | cursor: pointer; 96 | } 97 | 98 | .btn:active { 99 | background: #19A8FF; 100 | } 101 | 102 | .btn { 103 | display: inline-block; 104 | padding: 6px 10px; 105 | margin-bottom: 0; 106 | font-size: 13px; 107 | font-weight: normal; 108 | line-height: 1.42857143; 109 | text-align: center; 110 | white-space: nowrap; 111 | vertical-align: middle; 112 | -ms-touch-action: manipulation; 113 | touch-action: manipulation; 114 | cursor: pointer; 115 | -webkit-user-select: none; 116 | -moz-user-select: none; 117 | -ms-user-select: none; 118 | user-select: none; 119 | background-image: none; 120 | border: 1px solid transparent; 121 | border-radius: 2px; 122 | } 123 | 124 | input, button, select, textarea { 125 | font-family: inherit; 126 | font-size: inherit; 127 | line-height: inherit; 128 | } 129 | 130 | .form-input-div input { 131 | width: 350px; 132 | height: 42px; 133 | padding-left: 39px; 134 | border: 1px solid #E1E2E6; 135 | } 136 | 137 | .err_msg { 138 | position: relative; 139 | color: #fc4949; 140 | height: 20px; 141 | line-height: 20px; 142 | } 143 | 144 | #form-inputs { 145 | width: 350px; 146 | margin-top: 30px; 147 | text-align: left; 148 | } 149 | 150 | #form-title { 151 | font-size: 40px; 152 | color: #172B4D; 153 | text-align: center; 154 | margin-top: 60px; 155 | } 156 | 157 | .login { 158 | height: 1024px; 159 | background-color: #f7f8fa; 160 | font-family: "PingFang SC", "Helvetica Neue", Helvetica, "Hiragino Sans GB", STHeitiSC-Light, "Microsoft YaHei", "微软雅黑", Arial, sans-serif; 161 | font-size: 13px; 162 | line-height: 1.42857143; 163 | color: #333; 164 | } 165 | -------------------------------------------------------------------------------- /src/assets/styles/icon-font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/FasterWeb/10b5c2471802c52cefd478c237c884fc36ba1f95/src/assets/styles/icon-font/iconfont.eot -------------------------------------------------------------------------------- /src/assets/styles/icon-font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/FasterWeb/10b5c2471802c52cefd478c237c884fc36ba1f95/src/assets/styles/icon-font/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/styles/icon-font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/FasterWeb/10b5c2471802c52cefd478c237c884fc36ba1f95/src/assets/styles/icon-font/iconfont.woff -------------------------------------------------------------------------------- /src/assets/styles/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('./icon-font/iconfont.eot?t=1551323395845'); /* IE9 */ 3 | src: url('./icon-font/iconfont.eot?t=1551323395845#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAABcYAAsAAAAAKzwAABbKAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCIcgq/KLMTATYCJAOBJAtUAAQgBYRtB4NiGwkkVQQ2DkAQ5Psg+/9TAjdkwvvQVyVEu2uqj7QBiUamNZX2Z2XqyNx6KkUWYPVBU6NEh9TMSvzBDZe9WHBBbj4hxKWIScXBNu/Cj2PHx/W+QykJiv1+++Qj7iGKTmcIzSxEEk00JOiUaqkdJqF8EJjbACvqORyetvnv7qh2gAoogoVRhDa0YIGBW+NcqEtMWLPG6TKsrXHfVTg3FuV0mf5VukoCAL6v22RZQgknckmggTefOzjUNH9/F+QqLc3P5YfaLGcCK5mBsG2RyKkIDCfqVKB1aB2JrEnkTHZPxBrpy1+9fkUnznUTijwf0CY89aj/B1pUcYXo58DG7zVV3fqU2xkWFZT02+09bY3aA68CCvp3TBA2YQ59Qf69rtYmZwYB7y4DQR/49CZFSVX9l927zY8cm/MVJLdYLsgGE6u0qCiqH3QX3cUXscq8t4qhqag6StqSoumBWiZjwTz8BXKksaP1Z2x+PvgVuEJR5xCwEbGZd31zElTo8RHLuqwCgJMjagILHJaVDAW4RI1cICTKqko4l83T5BpaWXqSdgHgiv745APEJSOpQoYkXNnWDgeKAJ+KLIoxqOusqi6Ep8uRYQpy0mNuLDkCSOhMUSo+PixUsgKAaRNNV8Aw8MhovLBx+YsjkyqDSR6LUuNNsUTX87svsGU0oy11vo+7KBYDFMzF8UzBT+ygycM4duMQOzHt93NVvE7Qyxg0Z+h5rwIo1ePiGJXpGLoVDqE2kUCmla1StQGaPil99P6Tl+pQG7USsdxq4+m3IymB5q+C5BdmjCHAESglIKAHAfvEOgaACgIHjCLwQBOCDHQhaKoiuhcA3QimpAUBjCDYQC3BBdoQfkAdwh+oIWKBjIgDWhEyoJxIBiqJFKCaSAUGEBlATpiAPkSeSm/dAkCBKAUSYixQRowDUmI80IGYArQjlqhM0LsA6ETcBRrwggXq8ZIBNONlBjCIly3AEF46gSq+44B+/BdLGQbGz5dYBdyBEeX/wyoBqYUKaoIiP0IB2v1Q3xMtOmtcQqOfaelzhAbFTlLmUxo0vNr8qrLvF8lqbh+9mCqlaCL2aY0LlsX3IXIMXyJ+fa2fKqb8E/0s246GS/pLhAUoXCYQskhMMWTLvDMwDIhhQn6aZbxyieGYwE0wYej3sWEGv0HjkNAnVBh8YNNoeP/rQdI4P8P6g5T7sWh0KufHSoJ8fGJhC9FlZTU9XVhCa4H6OuiLNZ25pi5Cxdsu41ZdNsesWUQpsjo8ZfUcaZ5LVBX70NIjv/FJrcQ00szLQUeQXTPJk3jabA7Kbpf6y/oVbFUR1enc1FNJSWZfhNB0zbiqbUib3dbZ0ViDJMEVO5GzJn9KcLAFenFxHfQ0q0EAa2MIidBoh5y9DICATFOk1nqWsFpjdMtht7Q2uT3rRMk05q6moLM9l65oBnMXGv0QKtvl0WSngGmGqdd8S8ugYXXF524zaxkhsLLuUeL6K/qodc0PBRqIXu4UaGzDEIGanJa7546QyEuQDy02hrubafJY156wPmu5Cx2khFRmHYLu595yfrWd5JEBdMRlXg4BtOKJ35jOeo0wAKE4iFcoaF8RCzAcNC+mCIKvmWp6EiGdVRw9aYrEtpub7xOKEG0+hy+KYoFtDbtSUvMDjTcyGEPivtiWoHCqZ6V0XgbYkoORjPHW5QAEHbQyEnTNoTkGOCkmZu5EBk0zr0JIzbNbv/D7OU7/osqjCkEMj9u7OHpGKwup5CCALAU5k7zW6llzzhKl+LJ0IQWLWUnUTttlYVo7Dr93LeOe74IuUGrmRATG3R4kfREhM+8mJ48CyOF0V1KDidncfDTY5RTtCoUTFh4hu9MpdjL2NQHXJVMLtJ739Y2BnhKwZ0HsBk9ZbhpSdsqYswwAO3R+iUzcs7xM4ud52dZTlqQqTgO20AtG6XpG2tRMV/TyaS9FgG7b4gbXkAbN5QBg3ZF2kV1sG22wlFwqeY2pZJ2QqUK4ajU0PloW+VbLglXlEJQEGGdlWYF8ykSykclNzcqwSZb19tBipZxKhYFGPf6g5Jk1xSk/K5KsSN7qyCy75Pygi0NAcy+ooQc8RgNPahn8uOhiufXJ6GSbPw3r3vTKDhPLriPvqsPzdaT0WKpBOjH2dOGWlbvRz1G8G1cHjmB6zbM6O1F7s9O8767ebBSWHNUQZBZAG/UlSI0thbdqzR39cUOAKQ9Q3CrTBkB3LA9CQ91qJV222Ko2rJYbeGPs1Ni8KJehqOro4cSL3isTdYxM5CyKPFptyRGtV64SVU5mr+d7iFe9cOH6rWPki3mCeaXI69WCyaJRG8ZmCrdy3YUrrG1aB+WcLMxCElU7asMuUq3X/KbK9jPFAimwcksCR3INijXWtdUfUYgyOFrmadfkXkvJJuu4V6bivPbMGDTLXLtxW/QibiO/TlaQbKdH1pI2Zu6sC1rjRG9y4zAydpMbT0ico+g2f5CBsgLZYrgoYnoKsw00rjbzM9pn5H9Tr0Oh7/DtU7JbRpxpY6j/sDYRNdOTFrthUpnyBTxPEEk8jPWf0H55KNJX8iQsCOaQ1Dch4SxOmOCKROY0IhE4MUFRpcgTPlH4mOogLKnIhuRW4cIw4vmCYNB3jdhhxfoFyIEVAXugGgmZndW1DUP/weHogj5ypGFzNoJxQxxgagHHZfj2DIXC6H8nWZJKdF25hia0YYHMy0F5LWbgoJuL4jNlelBPCWDNb2Ug9KyldwL/ypa3eMqdrSTaWrVDCS36sCX8NLOeJ17CNWJtbv1UdxmZcepiHqw85j13vJBaTZJiR10oPjjG6R4qRvdmC/qLum+nX13XmDZOWMf0pJbtcu5RCp+af++OBUyje+lqR1q7as3jfbINRmglWzHXPfPH3sdwEapc5j3mMqasO4eZKAvVYCwuZK3cRbxt4GIyT93pYM7TlJEo10SMqW89GbB3sr2v+ipgzPLqcUgZ3rVLmLWKK8cAp3mkmQTwpEarNFOGibTZe20A9Ox1M38TQuPtn6sFY3Lfn6wP33lP6c/nvZz+bZ0SzLEX6DYKOPUm44O5jX+dkxf1FC17tQYYMPMufrXefg+SzZDvPxxskt095dFa0kxXZVaD6qxmp+VB6E7OfF4cIshemG4ZHZRq0bKH3YzMICiHipIA43a2b3Gbnen1FdJazxAPMmfrVoCDjJYoSFU32ho5aplgmEAXW9QKLY61/PxzxiqyNJFeh5bgxYg63u6ig4wPrd+I1nEgEL9g2av/R304xf40zhj8e1zHcUrQ9/Jn359o+u329b6vtlfLbpz//f37WkDGd23XF7//aD8XQiku/cUvpV9s/oLe+2FkIJGm3tF+nV8eX0bkv6OYKV+S/03VA4eKOd7R6b8sa2zX6XiwQ7788N0vby4KRrWf/97gugl1+lK8br/v8VqujRrvzyEjghYTqrfOJlDWTAxd+bC9FcP0ZmJaY/0oXo+Bi9vd09PNdYELtCXELUm/5vjMSf4NfBETv1lM4BPEzXRPp8BV0a+HuYoqhXBVSAPIPq23bRQbqq/E9MIGxBPzAT/RH8J7GBUo5X+gz4Ub5Y3os9rJdDv/yiaxIR1lp62EQ4DHgz569ENJs7lFT/zodjSQHsfUk0vc4e4Scv2INk6+sbmsOdvIabv2cw4AqVfSnL1pIDn3pl21cs9vy1BYzCloDEkDQ2LqRY0HrGvXXOjbi363+B8m5hTldfMvQC+tlzNr8CxC792eo4ROne9IXo3WF8Qz6rdsrZjb+cR3TueWPTa73aepOXrr1h9zFtttE7YsWbply9wRyRcD5P8n0m30hWNFmTNntQtKhK1Tpgg6a5f2+KXkpfAV9Fv9nM6qmd0MajW91bEoUD9yId0WoMo6Mm5se4Bd0D52nG9NbxfYA9of9Ah/EVrsuFXmmhqXB81g/syOWVQhxUYW+gnJNoowP3au0MqaXKImQcPNfUQHtikA6gQFFFcXG/X/csdy0fKSo8e05ecONDuaRc0lnse05rMHmhxNoqYS7+hpTR0HZN4nUFB/Z/qOTmgCXLG1aUbms2LFkKfPypVDsqERsHNy8LRsNGoHqpw1dL2kNaSYOI/W/lSH4w/dgI+c5TynLHWMCwHp+6qwBoO1qkgt2VXsTIp4LWNWNy4aI09TptLTCBA+OiA3j/6EA/k+Ce6EZuQATpogN3dN31lY7+Py/iGuXLQ6VLElvegiZvlXpor51RpjNRjAV/D2ihwOjAukitF79Efd/5E9lGPub4E0cU99j5QS+M39H8VDPvZa1yOm7adKqEePdH/GP2LI+kqbMe9I4NTSjrDxQ9O7y8as4Wl5q9W62BlJHGKfxdU39JmreavrF6/laXggqacBu5UF8/SN/TallsGb8Yd71XO51czquTwV7TB+l9eL/hP52Tm2CfAtnAu/L7J2hChPYK3xNkbOls9S98ttD7mKwR3GN5i+mj4vxR8GfER5BPXnDy2lYzAuIx3I7z90B+qIsfAT/grk/HlkJbLC6IpHjBAI6TkUatKLBYnn7MDjGNiru67Y316FamDPT68UJCz58vUm7vIvS5bTT7XcTeHWatJDSxtrzWbuCk2u0nYFbYWMtlKDngMQm/pbirpmJnSVleUJm8WR2SJj6EZDXFb0Zm6CVqKnbr1cu2jU2Ns58bucebRR66BxSZrEM9DviqdYK0Ye2qsDmWByQXD6k9PF53l0Lp0Cy3WBH2KGUp4nDY17F/hWYWDzzhefftKkns+fWGpyFNnOfNeUoIHKKQe8WXplpGO2dfI2hSHFoNim8lVD/ftD6iSNp0DEAmJlLzcBrmloqEGBg3CWBK71PdaNfW8tpfHhCdCQqVOHoEBBYCe4vb6XukvF40keUnJp3mgR4PnWiFy7hrjSCMIqUZz6EOr7dxrh8Pdv9ahvsEhStbYd3gvv60nDq3TtF3HyWYStVaMGfIcOJeld1Si8qGkf3K7VpbfrrtB++HC42GHdftUh6PsACUXdCIHICxgV5oK5zpycEiN5HZYinZ50LCV3xlzbjBm8XN9wSVHoSIvFYDCbl159YPDPAumcdeZR0Lo2AYvldpVMtkUijxGWAeXlAwbo9MSCy6kju1isrnd6WfY2VAPwWDqgP/1R6uYie7cOKLaDO7i7Tem4XiViA0lABU1CRSY0t0yAM9jSooRUjb+EAvTS6SNGTJcuniHd5E5soGpyKRmUWvlq+Sq5U++/aLL3UrctXOTimXmuRQvXeAJQl6UOMs0CefDEDRsmSoEHIQWQZ5qlDloYtfMt3JCEbH/6dLspCITGJLjhPx6JgIpnzChGgWIXqDh6r8C391KJhG/X6ebCaomUOmkOD1rK6NFQGMvCbbL42GFmrdYsDZTJbFAAdX49OG28fOb0ZeeiLq+uRWBpYvaGZ+5nxzdkF8KVlbArfT1xISRz03zpZJkVXrECLoetlhLwRCaM4VpkWZnd25waVX60xWd4BQti7pSc2y5mZ1pko7nOIWf2ZOcPTy33t0M2Myd/w/56TUY/2XDOKAjkfu/kqT65LB0Um+RtNIYLw4Im5ZsisOLQn6yYvkxFjjeKHyFnqgvS6Kp8akRW5BKOUARzc7yzfLLkaaLUoIn5JqYMhRGE0pVsQzY/JNoIyr0aCkpmiVpWyBVFrxZSlHSlNY5TWOYTkcJU9BWLY5i/WLFY4RBF0AQfnm+2j7G0zDsni0PVoUdlmI9RniwoKIos98az+CVZabyxpyiGSk2N7BCGhBtJxoxRYZHQrRTBitkMVWlaTB9qCiK1RIoMZZHKtyAlgivh+EXBAVLgBBE3jE/JVaItOF9u2EAYYSgeE1PSJwAFESmdw8VhMIcgg1oEhJZ0lTehQC6nDPBBSx3Xp1Dg5wPFF7UPdIxBE1jFOaasVM6HBSQwcJj0erQDrXGm0MT/+wmIdxgOyprmGQ0vWgSPdgN3kLNo/mSHAzKNGWNCgQbhCUxrIohlYgKd/DyMCvwAdeI5GTy3nDlT6yPwXfXjZ9rPZdOjvgKf2qc2zWGygN8VimjUntRBGYP8xQ5IpABxyx7C3GUVY3JGU4FSZ6JplUxBS0XmM6yX4Xb/LD2XgKHl0Iy0Ikr6sn5ZhlEUSKnNpWlVzAAJ7jnj5Z2KMXoeLoSek6m1H7M4PLI3rjfWw0cGnUeRSKjzRhpxRfRiiSRlFeZ+UVvRfQxACnV5eZg6Un/iEtZbU5vpLRpMrGuzJunDrA0OF70V31Au0ieWt+Fb6a7YmJ9t5dzuyhvwLoZLCNMnIVg4/wbrY421Td5uK0/UiyQQvMGPvTRzgLlXQzVSNdqbZv/EAjSgUADjKcP7W5XeuwQcb7U3R7DLW+mzixHurYY1mqrnjNUM9TGk9KqlyWq9AFMN5VAON/gNlPglIXJ8r0n8E/0HiiWBAxnXDwRrsjw/bw6xf5Z2YMdTYZA2oOiDLXh9nEHil6UB0J0zne5b3n+ceorHeu+/affmBjPGjvEHH3ahna8LMbYkcR86BD33jQjxh6JwAh8kXknYxMu9yQ2OSZddf130bsekHkMxKzOz/MblDlIXsfNsv/GpSvpI4UjBSLhwZovFOytr6E13B/kC4UZr1Eho55onBS/BUbpf+RWXetq6bLLDisxCYSHgI47baD4fLeX2QAhENnnp+VMoQk/5flyB75a++AcdVsL0H4ymV0GvwNsTd0E3xLFxBN0uGaHgfiW6IL0KZvMpVlowHUMPpp274s+eRvrCakOHP2WRTfNMZFbYU2ab/YnRx162jukTymbajUroYwdLPgPgEonCRK3bpplnSzDNI/x1AJfnD3IcHkTFUmQf3CsucRm+nd5ObsQEAADIA/wdAM92s5ACT+x0FklzLjiHyJJCBSIV63mQffREkWyVcI/QxGNOIErngPOIgTf+GclUkcE4TsWj1iOK1m5jxPMOIPGOwzUkG7Lc3QGA7MD/nKUuI2OTkTX4NAi38APlatQg0UUibsSB8HcgBOcZM8JJChfwWXuDB8hg6PQXWYMgwiH8OvxBX50RRW7gdp2047qFr0ipcj4AkDn46z+czwbK7SdzLNSUrzji/6WPut1QgdCYpxQFmm/CgZEoBPwzOYrSgP9WVX5WYir6+rvBcScuF/Vp3EkETQZUGwYAwd8FgH8yT2SAh2YZTGX/v+YNZiTBsHgSAieQR+VGSGhEEgkDRy4RhIP0KxN5gQqAgHI8ACAMzPYkCA3skGAUxyQEDXTzqNwHEhoLvJMwaBAiERRCXikSBUM63Kk9FAS1KIPAyllV9bQLFnwHzV3h45W0/Bv8iFOFjNPrWRew4KtIMl51RqSE8q4XZ+/WoOucGLxroKLYEA1vSaJ4SePK9ZOd2kNB5+5rUYYBsXJWHZl2qe//Dpq7wgvW+DHgN/gRT13IOFXgL5pVWqMui49XnREKKXFc3rtenFEIOt9xYuCfqYGKYlPAHt4StCWlqsXNG/2y0ibfWsml8ZuUStMN0/r3z3u1HdfzxRKpTK5QqtQarU5vMJrMFqvN7nC63J5QlgXHNyysXpWF03X6TShLtDXcZmm2H12vESiVr08z+bxSif0XuMyaa6f43+DJa1lDyXo9GmxKMe+6ki7NhYZoCGScjQhutC4/3PNGaLauIS/jmBgPNVqdaz4P9o83EpZcVpDua9FjXxx/2dXwT+Beyb1qEFrH9SzODbe8CEW3H+BNOYOGMjUjY1usDCjOe0PWBa7rqZWH4qjXUbbDHa4QnT6by2d5KsHWWOIr6lG17ZlquZrnEdTLzhqbpuvkSdtQh6Be61bG2uvA1rxgG+cpbGV4bbOwnAA=') format('woff2'), 5 | url('./icon-font/iconfont.woff?t=1551323395845') format('woff'), 6 | url('./icon-font/iconfont.ttf?t=1551323395845') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('./icon-font/iconfont.svg?t=1551323395845#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-youxiang:before { 19 | content: "\e668"; 20 | } 21 | 22 | .icon-baogao:before { 23 | content: "\e66e"; 24 | } 25 | 26 | .icon-ybbindex:before { 27 | content: "\e631"; 28 | } 29 | 30 | .icon-02:before { 31 | content: "\e64a"; 32 | } 33 | 34 | .icon-iconset0196:before { 35 | content: "\e65f"; 36 | } 37 | 38 | .icon-17:before { 39 | content: "\e610"; 40 | } 41 | 42 | .icon-xiazai:before { 43 | content: "\e600"; 44 | } 45 | 46 | .icon-index:before { 47 | content: "\e66d"; 48 | } 49 | 50 | .icon-fuzhifuzhi:before { 51 | content: "\e636"; 52 | } 53 | 54 | .icon-debug:before { 55 | content: "\e606"; 56 | } 57 | 58 | .icon-shijian:before { 59 | content: "\e63b"; 60 | } 61 | 62 | .icon-language-python-text:before { 63 | content: "\e7ca"; 64 | } 65 | 66 | .icon-xiangmu:before { 67 | content: "\e63c"; 68 | } 69 | 70 | .icon-jiankong:before { 71 | content: "\e61f"; 72 | } 73 | 74 | .icon-houtui:before { 75 | content: "\e617"; 76 | } 77 | 78 | .icon-dingshirenwu:before { 79 | content: "\e61e"; 80 | } 81 | 82 | .icon-xiangmu1:before { 83 | content: "\e707"; 84 | } 85 | 86 | .icon-ceshi:before { 87 | content: "\e6da"; 88 | } 89 | 90 | .icon-mima:before { 91 | content: "\e652"; 92 | } 93 | 94 | .icon-quanju_yuming:before { 95 | content: "\e609"; 96 | } 97 | 98 | .icon-jiekou:before { 99 | content: "\e74a"; 100 | } 101 | 102 | .icon-shujuku:before { 103 | content: "\e615"; 104 | } 105 | 106 | .icon-yali:before { 107 | content: "\e632"; 108 | } 109 | 110 | .icon-xingmingyonghumingnicheng:before { 111 | content: "\e61c"; 112 | } 113 | 114 | .icon-xiugai:before { 115 | content: "\e67d"; 116 | } 117 | 118 | .icon-houtui1:before { 119 | content: "\e66f"; 120 | } 121 | 122 | .icon-yumingguanli:before { 123 | content: "\e6cc"; 124 | } 125 | 126 | .icon-yonghuming:before { 127 | content: "\e60d"; 128 | } 129 | 130 | .icon-houtui2:before { 131 | content: "\e613"; 132 | } 133 | 134 | .icon-171:before { 135 | content: "\e601"; 136 | } 137 | 138 | .icon-bendibianliang:before { 139 | content: "\e692"; 140 | } 141 | 142 | .icon-youxiang1:before { 143 | content: "\e650"; 144 | } 145 | 146 | .icon-config:before { 147 | content: "\ee32"; 148 | } 149 | 150 | .icon-fuzhi:before { 151 | content: "\e63d"; 152 | } 153 | 154 | .icon-shujuku1:before { 155 | content: "\e782"; 156 | } 157 | 158 | .icon-xiangmuliebiao:before { 159 | content: "\e7a7"; 160 | } 161 | 162 | .icon-python:before { 163 | content: "\f216"; 164 | } 165 | 166 | .icon-yunhang:before { 167 | content: "\e616"; 168 | } 169 | 170 | .icon-shanchu:before { 171 | content: "\e608"; 172 | } 173 | 174 | .icon-xiazai1:before { 175 | content: "\e602"; 176 | } 177 | 178 | -------------------------------------------------------------------------------- /src/assets/styles/reports.css: -------------------------------------------------------------------------------- 1 | .success { 2 | font-weight: bold; 3 | font-size: medium; 4 | color: #67c23a; 5 | } 6 | 7 | .error { 8 | font-weight: bold; 9 | font-size: medium; 10 | color: red; 11 | } 12 | 13 | .failure { 14 | font-weight: bold; 15 | font-size: medium; 16 | color: salmon; 17 | } 18 | 19 | .skipped { 20 | font-weight: bold; 21 | font-size: medium; 22 | color: #909399; 23 | } 24 | 25 | .POST { 26 | color: #49cc90; 27 | } 28 | 29 | 30 | .PUT { 31 | color: #fca130; 32 | } 33 | 34 | .GET { 35 | color: #61affe; 36 | } 37 | 38 | 39 | .DELETE { 40 | color: #f93e3e; 41 | } 42 | 43 | 44 | .PATCH { 45 | color: #50e3c2; 46 | } 47 | 48 | .HEAD { 49 | color: #e6a23c; 50 | } 51 | 52 | .OPTIONS { 53 | color: #409eff; 54 | } 55 | 56 | 57 | .code-block { 58 | 59 | border: 1px solid #ebedef; 60 | border-radius: 4px; 61 | color: #222 !important; 62 | font-family: Consolas,monospace; 63 | font-size: 13px; 64 | margin: 0; 65 | padding: 7px 10px; 66 | overflow: auto; 67 | } 68 | -------------------------------------------------------------------------------- /src/assets/styles/swagger.css: -------------------------------------------------------------------------------- 1 | .block_post { 2 | border: 1px solid #49cc90; 3 | background-color: rgba(73, 204, 144, .1) 4 | } 5 | 6 | .block_method_post { 7 | background-color: #49cc90; 8 | } 9 | 10 | .block_put { 11 | border: 1px solid #fca130; 12 | background-color: rgba(252, 161, 48, .1) 13 | } 14 | 15 | .block_method_put { 16 | background-color: #fca130; 17 | } 18 | 19 | .block_get { 20 | border: 1px solid #61affe; 21 | background-color: rgba(97, 175, 254, .1) 22 | } 23 | 24 | .block_method_get { 25 | background-color: #61affe; 26 | } 27 | 28 | .block_delete { 29 | border: 1px solid #f93e3e; 30 | background-color: rgba(249, 62, 62, .1) 31 | } 32 | 33 | .block_method_delete { 34 | background-color: #f93e3e; 35 | } 36 | 37 | .block_patch { 38 | border: 1px solid #50e3c2; 39 | background-color: rgba(80, 227, 194, .1) 40 | } 41 | 42 | .block_method_patch { 43 | background-color: #50e3c2; 44 | } 45 | 46 | .block_head { 47 | border: 1px solid #e6a23c; 48 | background-color: rgba(230, 162, 60, .1) 49 | } 50 | 51 | .block_method_head { 52 | background-color: #e6a23c; 53 | } 54 | 55 | .block_options { 56 | border: 1px solid #409eff; 57 | background-color: rgba(64, 158, 255, .1) 58 | } 59 | 60 | .block_method_options { 61 | background-color: #409eff; 62 | } 63 | 64 | .block { 65 | position: relative; 66 | border-radius: 4px; 67 | height: 43.6px; 68 | padding: 5px; 69 | display: flex; 70 | align-items: center; 71 | } 72 | 73 | .block_url { 74 | word-break: normal; 75 | width: auto; 76 | display: block; 77 | white-space: pre-wrap; 78 | word-wrap: break-word; 79 | overflow: hidden; 80 | -webkit-box-flex: 1; 81 | -ms-flex: 1; 82 | font-family: Open Sans, sans-serif; 83 | color: #3b4151; 84 | } 85 | 86 | .block_name { 87 | padding-left: 5px; 88 | word-break: normal; 89 | width: auto; 90 | display: block; 91 | white-space: pre-wrap; 92 | word-wrap: break-word; 93 | overflow: hidden; 94 | -webkit-box-flex: 1; 95 | -ms-flex: 1; 96 | font-family: Open Sans, sans-serif; 97 | color: #3b4151; 98 | } 99 | 100 | .block_method_color { 101 | cursor: pointer; 102 | color: #fff; 103 | } 104 | 105 | .block-method { 106 | font-size: 14px; 107 | font-weight: 600; 108 | min-width: 80px; 109 | padding: 6px 15px; 110 | text-align: center; 111 | border-radius: 3px; 112 | text-shadow: 0 1px 0 rgba(0, 0, 0, .1); 113 | font-family: Titillium Web, sans-serif; 114 | } 115 | 116 | .block-summary-description { 117 | word-break: normal; 118 | width: auto; 119 | display: block; 120 | white-space: pre-wrap; 121 | word-wrap: break-word; 122 | overflow: hidden; 123 | -webkit-box-flex: 1; 124 | -ms-flex: 1; 125 | font-family: Open Sans, sans-serif; 126 | color: #3b4151; 127 | } 128 | 129 | h4 { 130 | display: block; 131 | -webkit-margin-before: 1.33em; 132 | -webkit-margin-after: 1.33em; 133 | -webkit-margin-start: 0px; 134 | -webkit-margin-end: 0px; 135 | font-weight: bold; 136 | } 137 | -------------------------------------------------------------------------------- /src/assets/styles/tree.css: -------------------------------------------------------------------------------- 1 | ul li { 2 | list-style: none; 3 | } 4 | 5 | .operation-li { 6 | line-height: 60px; 7 | color: #555; 8 | font-size: 12px; 9 | width: 240px; 10 | margin-top: 1px; 11 | } 12 | 13 | .api-tree { 14 | padding: 0; 15 | margin: 0; 16 | } 17 | 18 | .nav-api-header { 19 | height: 48px; 20 | border-bottom: 1px solid #ddd; 21 | background-color: #F7F7F7; 22 | 23 | } 24 | 25 | .nav-api-side { 26 | overflow: auto; 27 | border: 1px solid #ddd; 28 | width: 280px; 29 | margin-left: 10px; 30 | border-radius: 4px; 31 | position: fixed; 32 | top: 110px; 33 | bottom: 0; 34 | 35 | } 36 | 37 | .custom-tree-node { 38 | flex: 1; 39 | display: flex; 40 | align-items: center; 41 | justify-content: space-between; 42 | font-size: 14px; 43 | padding-right: 8px; 44 | } 45 | 46 | 47 | .tree { 48 | overflow-y: auto; 49 | overflow-x: auto; 50 | width: 280px; 51 | height: 500px; 52 | } 53 | 54 | .el-tree { 55 | min-width: 100%; 56 | display: inline-block !important; 57 | } 58 | 59 | .el-tree-node__expand-icon { 60 | padding: 4px !important; 61 | } 62 | 63 | .el-tree-node__content { 64 | height: 50px; 65 | } 66 | 67 | .el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content { 68 | background-color: rgba(64, 158, 255, .1); 69 | color: #409EFF; 70 | } 71 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import ElementUI from 'element-ui' 5 | import 'element-ui/lib/theme-chalk/index.css' 6 | import App from './App' 7 | import router from './router' 8 | import 'styles/iconfont.css' 9 | import 'styles/swagger.css' 10 | import 'styles/tree.css' 11 | import 'styles/home.css' 12 | import 'styles/reports.css' 13 | import * as api from './restful/api' 14 | import store from './store' 15 | 16 | Vue.config.productionTip = false 17 | Vue.use(ElementUI) 18 | Vue.prototype.$api = api 19 | 20 | Vue.filter('datetimeFormat', function (time, format = 'YY-MM-DD hh:mm:ss') { 21 | let date = new Date(time); 22 | let year = date.getFullYear(), 23 | month = date.getMonth() + 1, 24 | day = date.getDate(), 25 | hour = date.getHours(), 26 | min = date.getMinutes(), 27 | sec = date.getSeconds(); 28 | let preArr = Array.apply(null, Array(10)).map(function (elem, index) { 29 | return '0' + index; 30 | }); 31 | 32 | let newTime = format.replace(/YY/g, year) 33 | .replace(/MM/g, preArr[month] || month) 34 | .replace(/DD/g, preArr[day] || day) 35 | .replace(/hh/g, preArr[hour] || hour) 36 | .replace(/mm/g, preArr[min] || min) 37 | .replace(/ss/g, preArr[sec] || sec); 38 | 39 | return newTime; 40 | }); 41 | 42 | Vue.filter("timestampToTime", function (timestamp) { 43 | let date = new Date(timestamp * 1000); 44 | const Y = date.getFullYear() + '-'; 45 | const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; 46 | const D = date.getDate() + ' '; 47 | const h = date.getHours() + ':'; 48 | const m = date.getMinutes() + ':'; 49 | const s = date.getSeconds(); 50 | 51 | return Y + M + D + h + m + s; 52 | 53 | }); 54 | Vue.prototype.setLocalValue = function (name, value) { 55 | if (window.localStorage) { 56 | localStorage.setItem(name, value); 57 | } else { 58 | alert('This browser does NOT support localStorage'); 59 | } 60 | }; 61 | Vue.prototype.getLocalValue = function (name) { 62 | const value = localStorage.getItem(name); 63 | if (value) { 64 | return value; 65 | } else { 66 | return ''; 67 | } 68 | }; 69 | 70 | router.beforeEach((to, from, next) => { 71 | /* 路由发生变化修改页面title */ 72 | setTimeout((res) => { 73 | if (to.meta.title) { 74 | document.title = to.meta.title 75 | } 76 | 77 | if (to.meta.requireAuth) { 78 | if (store.state.token !== '') { 79 | next(); 80 | } else { 81 | next({ 82 | name: 'Login', 83 | }) 84 | } 85 | } else { 86 | next() 87 | } 88 | }) 89 | 90 | }) 91 | 92 | /* eslint-disable no-new */ 93 | new Vue({ 94 | el: '#app', 95 | router, 96 | store, 97 | components: {App}, 98 | template: '', 99 | created() { 100 | if (this.getLocalValue("token") === null) { 101 | this.setLocalValue("token", ""); 102 | } 103 | if (this.getLocalValue("user") === null) { 104 | this.setLocalValue("user", ""); 105 | } 106 | if (this.getLocalValue("routerName") === null) { 107 | this.setLocalValue("routerName", "ProjectList"); 108 | } 109 | this.$store.commit("isLogin", this.getLocalValue("token")); 110 | this.$store.commit("setUser", this.getLocalValue("user")); 111 | this.$store.commit("setRouterName", this.getLocalValue("routerName")); 112 | 113 | } 114 | }) 115 | -------------------------------------------------------------------------------- /src/pages/auth/Login.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 129 | 130 | 133 | -------------------------------------------------------------------------------- /src/pages/auth/Register.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 89 | 165 | 166 | 169 | -------------------------------------------------------------------------------- /src/pages/auth/components/Login.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 131 | 132 | 135 | -------------------------------------------------------------------------------- /src/pages/auth/components/Register.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 89 | 165 | 166 | 169 | -------------------------------------------------------------------------------- /src/pages/config/RecordConfig.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 163 | 164 | 168 | -------------------------------------------------------------------------------- /src/pages/config/components/ConfigBody.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 231 | 232 | 248 | -------------------------------------------------------------------------------- /src/pages/config/components/ConfigList.vue: -------------------------------------------------------------------------------- 1 | 102 | 103 | 225 | 226 | 231 | -------------------------------------------------------------------------------- /src/pages/fastrunner/case/components/TestBody.vue: -------------------------------------------------------------------------------- 1 | 150 | 151 | 309 | 310 | 326 | -------------------------------------------------------------------------------- /src/pages/fastrunner/config/RecordConfig.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 163 | 164 | 168 | -------------------------------------------------------------------------------- /src/pages/fastrunner/config/components/ConfigBody.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 223 | 224 | 240 | -------------------------------------------------------------------------------- /src/pages/fastrunner/config/components/ConfigList.vue: -------------------------------------------------------------------------------- 1 | 113 | 114 | 242 | 243 | 247 | -------------------------------------------------------------------------------- /src/pages/home/Home.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 34 | 35 | 41 | -------------------------------------------------------------------------------- /src/pages/home/components/Header.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 36 | 37 | 75 | -------------------------------------------------------------------------------- /src/pages/home/components/Side.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 62 | 63 | 75 | -------------------------------------------------------------------------------- /src/pages/httprunner/DebugTalk.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 125 | 126 | 138 | -------------------------------------------------------------------------------- /src/pages/httprunner/components/Extract.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 138 | 139 | 141 | -------------------------------------------------------------------------------- /src/pages/httprunner/components/Headers.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 214 | 215 | 217 | -------------------------------------------------------------------------------- /src/pages/httprunner/components/Hooks.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 135 | 136 | 138 | -------------------------------------------------------------------------------- /src/pages/httprunner/components/Parameters.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 150 | 151 | 153 | -------------------------------------------------------------------------------- /src/pages/httprunner/components/Validate.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 307 | 308 | 310 | -------------------------------------------------------------------------------- /src/pages/httprunner/components/Variables.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 245 | 246 | 249 | -------------------------------------------------------------------------------- /src/pages/project/ProjectDetail.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 92 | 93 | 154 | -------------------------------------------------------------------------------- /src/pages/reports/DebugReport.vue: -------------------------------------------------------------------------------- 1 | 188 | 189 | 227 | 228 | 231 | -------------------------------------------------------------------------------- /src/pages/task/Tasks.vue: -------------------------------------------------------------------------------- 1 | 149 | 150 | 216 | 217 | 221 | -------------------------------------------------------------------------------- /src/restful/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import store from '../store/state' 3 | import router from '../router' 4 | import {Message} from 'element-ui'; 5 | 6 | 7 | 8 | export const baseUrl = "http://localhost:8000"; 9 | axios.defaults.withCredentials = true; 10 | axios.defaults.baseURL = baseUrl; 11 | 12 | axios.interceptors.request.use(function (config) { 13 | if (config.url.indexOf("/api/fastrunner/project/?cursor=") !== -1 ) { 14 | } 15 | else if (!config.url.startsWith("/api/user/")) { 16 | config.url = config.url + "?token=" + store.token; 17 | } 18 | return config; 19 | }, function (error) { 20 | return Promise.reject(error); 21 | }); 22 | 23 | axios.interceptors.response.use(function (response) { 24 | 25 | return response; 26 | }, function (error) { 27 | try { 28 | if (error.response.status === 401) { 29 | router.replace({ 30 | name: 'Login' 31 | }) 32 | } 33 | 34 | if (error.response.status === 500) { 35 | Message.error({ 36 | message: '服务器内部异常, 请检查', 37 | duration: 1000 38 | }) 39 | } 40 | } 41 | catch (e) { 42 | Message.error({ 43 | message: '服务器连接超时,请重试', 44 | duration: 1000 45 | }) 46 | } 47 | }); 48 | 49 | // user api 50 | export const register = params => { 51 | return axios.post('/api/user/register/', params).then(res => res.data) 52 | }; 53 | 54 | export const login = params => { 55 | return axios.post('/api/user/login/', params).then(res => res.data) 56 | }; 57 | 58 | 59 | // fastrunner api 60 | export const addProject = params => { 61 | return axios.post('/api/fastrunner/project/', params).then(res => res.data) 62 | }; 63 | 64 | export const deleteProject = config => { 65 | return axios.delete('/api/fastrunner/project/', config).then(res => res.data) 66 | }; 67 | 68 | export const getProjectList = params => { 69 | return axios.get('/api/fastrunner/project/').then(res => res.data) 70 | }; 71 | 72 | export const getProjectDetail = pk => { 73 | return axios.get('/api/fastrunner/project/' + pk + '/').then(res => res.data) 74 | }; 75 | 76 | export const getPagination = url => { 77 | return axios.get(url).then(res => res.data) 78 | }; 79 | 80 | export const updateProject = params => { 81 | return axios.patch('/api/fastrunner/project/', params).then(res => res.data) 82 | }; 83 | 84 | export const getDebugtalk = url => { 85 | return axios.get('/api/fastrunner/debugtalk/' + url + '/').then(res => res.data) 86 | }; 87 | 88 | export const updateDebugtalk = params => { 89 | return axios.patch('/api/fastrunner/debugtalk/', params).then(res => res.data) 90 | }; 91 | 92 | export const runDebugtalk = params => { 93 | return axios.post('/api/fastrunner/debugtalk/', params).then(res => res.data) 94 | }; 95 | 96 | export const getTree = (url, params) => { 97 | return axios.get('/api/fastrunner/tree/' + url + '/', params).then(res => res.data) 98 | }; 99 | 100 | export const updateTree = (url, params) => { 101 | return axios.patch('/api/fastrunner/tree/' + url + '/', params).then(res => res.data) 102 | }; 103 | 104 | export const uploadFile = url => { 105 | return baseUrl + '/api/fastrunner/file/?token=' + store.token 106 | }; 107 | 108 | export const addAPI = params => { 109 | return axios.post('/api/fastrunner/api/', params).then(res => res.data) 110 | }; 111 | 112 | export const updateAPI = (url, params) => { 113 | return axios.patch('/api/fastrunner/api/' + url + '/', params).then(res => res.data) 114 | }; 115 | 116 | export const apiList = params => { 117 | return axios.get('/api/fastrunner/api/', params).then(res => res.data) 118 | }; 119 | 120 | export const delAPI = url => { 121 | return axios.delete('/api/fastrunner/api/' + url + '/').then(res => res.data) 122 | }; 123 | 124 | export const delAllAPI = params => { 125 | return axios.delete('/api/fastrunner/api/', params).then(res => res.data) 126 | }; 127 | 128 | export const getAPISingle = url => { 129 | return axios.get('/api/fastrunner/api/' + url + '/').then(res => res.data) 130 | }; 131 | 132 | 133 | 134 | export const getPaginationBypage = params => { 135 | return axios.get('/api/fastrunner/api/', params).then(res => res.data) 136 | }; 137 | 138 | 139 | 140 | export const addTestCase = params => { 141 | return axios.post('/api/fastrunner/test/', params).then(res => res.data) 142 | }; 143 | 144 | export const updateTestCase = (url, params) => { 145 | return axios.patch('/api/fastrunner/test/' + url + '/', params).then(res => res.data) 146 | }; 147 | 148 | export const testList = params => { 149 | return axios.get('/api/fastrunner/test/', params).then(res => res.data) 150 | }; 151 | 152 | export const deleteTest = url => { 153 | return axios.delete('/api/fastrunner/test/' + url + '/').then(res => res.data) 154 | }; 155 | 156 | export const delAllTest = params => { 157 | return axios.delete('/api/fastrunner/test/', params).then(res => res.data) 158 | }; 159 | 160 | export const coptTest = (url, params) => { 161 | return axios.post('/api/fastrunner/test/' + url + '/', params).then(res => res.data) 162 | }; 163 | 164 | export const editTest = url => { 165 | return axios.get('/api/fastrunner/teststep/' + url + '/').then(res => res.data) 166 | }; 167 | 168 | export const getTestPaginationBypage = params => { 169 | return axios.get('/api/fastrunner/test/', params).then(res => res.data) 170 | }; 171 | 172 | export const addConfig = params => { 173 | return axios.post('/api/fastrunner/config/', params).then(res => res.data) 174 | }; 175 | 176 | export const updateConfig = (url, params) => { 177 | return axios.patch('/api/fastrunner/config/' + url + '/', params).then(res => res.data) 178 | }; 179 | 180 | 181 | export const configList = params => { 182 | return axios.get('/api/fastrunner/config/', params).then(res => res.data) 183 | }; 184 | 185 | export const copyConfig = (url, params) => { 186 | return axios.post('/api/fastrunner/config/' + url + '/', params).then(res => res.data) 187 | }; 188 | 189 | export const copyAPI = (url, params) => { 190 | return axios.post('/api/fastrunner/api/' + url + '/', params).then(res => res.data) 191 | }; 192 | 193 | export const deleteConfig = url => { 194 | return axios.delete('/api/fastrunner/config/' + url + '/').then(res => res.data) 195 | }; 196 | export const delAllConfig = params => { 197 | return axios.delete('/api/fastrunner/config/', params).then(res => res.data) 198 | }; 199 | 200 | export const getConfigPaginationBypage = params => { 201 | return axios.get('/api/fastrunner/config/', params).then(res => res.data) 202 | }; 203 | 204 | export const getAllConfig = url => { 205 | return axios.get('/api/fastrunner/config/' + url + '/').then(res => res.data) 206 | }; 207 | 208 | 209 | export const runSingleAPI = params => { 210 | return axios.post('/api/fastrunner/run_api/', params).then(res => res.data) 211 | }; 212 | 213 | export const runAPIByPk = (url, params) => { 214 | return axios.get('/api/fastrunner/run_api_pk/' + url + '/', params).then(res => res.data) 215 | }; 216 | 217 | export const runAPITree = params => { 218 | return axios.post('/api/fastrunner/run_api_tree/', params).then(res => res.data) 219 | }; 220 | 221 | export const runSingleTestSuite = params => { 222 | return axios.post('/api/fastrunner/run_testsuite/', params).then(res => res.data) 223 | }; 224 | 225 | export const runSingleTest = params => { 226 | return axios.post('/api/fastrunner/run_test/', params).then(res => res.data) 227 | }; 228 | 229 | export const runTestByPk = (url, params) => { 230 | return axios.get('/api/fastrunner/run_testsuite_pk/' + url + '/', params).then(res => res.data) 231 | }; 232 | 233 | export const runSuiteTree = params => { 234 | return axios.post('/api/fastrunner/run_suite_tree/', params).then(res => res.data) 235 | }; 236 | 237 | export const addVariables = params => { 238 | return axios.post('/api/fastrunner/variables/', params).then(res => res.data) 239 | }; 240 | 241 | export const variablesList = params => { 242 | return axios.get('/api/fastrunner/variables/', params).then(res => res.data) 243 | }; 244 | 245 | export const getVariablesPaginationBypage = params => { 246 | return axios.get('/api/fastrunner/variables/', params).then(res => res.data) 247 | }; 248 | 249 | 250 | export const updateVariables = (url, params) => { 251 | return axios.patch('/api/fastrunner/variables/' + url + '/', params).then(res => res.data) 252 | }; 253 | 254 | export const deleteVariables = url => { 255 | return axios.delete('/api/fastrunner/variables/' + url + '/').then(res => res.data) 256 | }; 257 | 258 | export const delAllVariabels = params => { 259 | return axios.delete('/api/fastrunner/variables/', params).then(res => res.data) 260 | }; 261 | 262 | export const reportList = params => { 263 | return axios.get('/api/fastrunner/reports/', params).then(res => res.data) 264 | }; 265 | 266 | export const deleteReports = url => { 267 | return axios.delete('/api/fastrunner/reports/' + url + '/').then(res => res.data) 268 | }; 269 | 270 | export const getReportsPaginationBypage = params => { 271 | return axios.get('/api/fastrunner/reports/', params).then(res => res.data) 272 | }; 273 | 274 | export const delAllReports = params => { 275 | return axios.delete('/api/fastrunner/reports/', params).then(res => res.data) 276 | }; 277 | 278 | export const watchSingleReports = url => { 279 | return axios.get('/api/fastrunner/reports/' + url + '/').then(res => res.data) 280 | }; 281 | 282 | export const addTask = params => { 283 | return axios.post('/api/fastrunner/schedule/', params).then(res => res.data) 284 | }; 285 | export const taskList = params => { 286 | return axios.get('/api/fastrunner/schedule/', params).then(res => res.data) 287 | }; 288 | export const getTaskPaginationBypage = params => { 289 | return axios.get('/api/fastrunner/schedule/', params).then(res => res.data) 290 | }; 291 | export const deleteTasks = url => { 292 | return axios.delete('/api/fastrunner/schedule/' + url + '/').then(res => res.data) 293 | }; 294 | 295 | export const addHostIP = params => { 296 | return axios.post('/api/fastrunner/host_ip/', params).then(res => res.data) 297 | }; 298 | 299 | export const hostList = params => { 300 | return axios.get('/api/fastrunner/host_ip/', params).then(res => res.data) 301 | }; 302 | 303 | export const updateHost = (url, params) => { 304 | return axios.patch('/api/fastrunner/host_ip/' + url + '/', params).then(res => res.data) 305 | }; 306 | 307 | export const deleteHost = url => { 308 | return axios.delete('/api/fastrunner/host_ip/' + url + '/').then(res => res.data) 309 | }; 310 | 311 | export const getHostPaginationBypage = params => { 312 | return axios.get('/api/fastrunner/host_ip/', params).then(res => res.data) 313 | }; 314 | 315 | export const getAllHost = url => { 316 | return axios.get('/api/fastrunner/host_ip/' + url + '/').then(res => res.data) 317 | }; 318 | 319 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from '@/pages/home/Home' 4 | import Register from '@/pages/auth/Register' 5 | import Login from '@/pages/auth/Login' 6 | import ProjectList from '@/pages/project/ProjectList' 7 | import ProjectDetail from '@/pages/project/ProjectDetail' 8 | import DebugTalk from '@/pages/httprunner/DebugTalk' 9 | import RecordApi from '@/pages/fastrunner/api/RecordApi' 10 | import AutoTest from '@/pages/fastrunner/case/AutoTest' 11 | import GlobalEnv from '@/pages/variables/GlobalEnv' 12 | import ReportList from '@/pages/reports/ReportList' 13 | import RecordConfig from '@/pages/fastrunner/config/RecordConfig' 14 | import Tasks from '@/pages/task/Tasks' 15 | import HostAddress from '@/pages/variables/HostAddress' 16 | 17 | Vue.use(Router); 18 | 19 | export default new Router({ 20 | mode:'history', 21 | routes: [ 22 | { 23 | path: '/fastrunner/register', 24 | name: 'Register', 25 | component: Register, 26 | meta: { 27 | title: '用户注册' 28 | } 29 | }, { 30 | path: '/fastrunner/login', 31 | name: 'Login', 32 | component: Login, 33 | meta: { 34 | title: '用户登录' 35 | } 36 | }, { 37 | 38 | path: '/fastrunner', 39 | name: 'Index', 40 | component: Home, 41 | meta: { 42 | title: '首页', 43 | requireAuth: true 44 | }, 45 | children: [ 46 | { 47 | name: 'ProjectList', 48 | path: 'project_list', 49 | component: ProjectList, 50 | meta: { 51 | title: '项目列表', 52 | requireAuth: true, 53 | } 54 | }, 55 | { 56 | name: 'ProjectDetail', 57 | path: 'project/:id/dashbord', 58 | component: ProjectDetail, 59 | meta: { 60 | title: '项目预览', 61 | requireAuth: true, 62 | } 63 | 64 | }, 65 | { 66 | name: 'DebugTalk', 67 | path: 'debugtalk/:id', 68 | component: DebugTalk, 69 | meta: { 70 | title: '编辑驱动', 71 | requireAuth: true, 72 | } 73 | 74 | }, 75 | { 76 | name: 'RecordApi', 77 | path: 'api_record/:id', 78 | component: RecordApi, 79 | meta: { 80 | title: '接口模板', 81 | requireAuth: true 82 | } 83 | 84 | }, 85 | { 86 | name: 'AutoTest', 87 | path: 'auto_test/:id', 88 | component: AutoTest, 89 | meta: { 90 | title: '自动化测试', 91 | requireAuth: true 92 | } 93 | 94 | }, 95 | { 96 | name: 'RecordConfig', 97 | path: 'record_config/:id', 98 | component: RecordConfig, 99 | meta: { 100 | title: '配置管理', 101 | requireAuth: true 102 | } 103 | 104 | }, 105 | { 106 | name: 'GlobalEnv', 107 | path: 'global_env/:id', 108 | component: GlobalEnv, 109 | meta: { 110 | title: '全局变量', 111 | requireAuth: true 112 | } 113 | 114 | }, 115 | { 116 | name: 'Reports', 117 | path: 'reports/:id', 118 | component: ReportList, 119 | meta: { 120 | title: '历史报告', 121 | requireAuth: true 122 | } 123 | 124 | }, 125 | { 126 | name: 'Task', 127 | path: 'tasks/:id', 128 | component: Tasks, 129 | meta: { 130 | title: '定时任务', 131 | requireAuth: true 132 | } 133 | 134 | }, 135 | { 136 | name: 'HostIP', 137 | path: 'host_ip/:id', 138 | component: HostAddress, 139 | meta: { 140 | title: 'HOST配置', 141 | requireAuth: true 142 | } 143 | 144 | } 145 | ] 146 | }, 147 | 148 | ] 149 | }) 150 | 151 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import state from './state' 4 | import mutations from './mutations' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | state, 10 | mutations 11 | }) 12 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | isLogin(state, value) { 4 | state.token = value; 5 | }, 6 | 7 | setUser(state, value) { 8 | state.user = value; 9 | }, 10 | setRouterName(state, value) { 11 | state.routerName = value 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | routerName: null, 3 | token:null, 4 | user: null 5 | 6 | } 7 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/FasterWeb/10b5c2471802c52cefd478c237c884fc36ba1f95/static/.gitkeep -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/FasterWeb/10b5c2471802c52cefd478c237c884fc36ba1f95/static/favicon.ico --------------------------------------------------------------------------------