[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    [\"env\", {\n      \"modules\": false,\n      \"targets\": {\n        \"browsers\": [\"> 1%\", \"last 2 versions\", \"not ie <= 9\"]\n      },\n      \"useBuiltIns\": true\n    }],\n    \"stage-2\"\n  ],\n  \"plugins\": [\n    \"transform-runtime\",\n    \"syntax-dynamic-import\"\n  ],\n  \"env\": {\n    \"test\": {\n      \"presets\": [\n        \"env\",\n        \"stage-2\"\n      ],\n      \"plugins\": [\n        \"istanbul\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".eslintignore",
    "content": "build/*.js\nconfig/*.js\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  parser: 'babel-eslint',\n  parserOptions: {\n    sourceType: 'module'\n  },\n  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style\n  extends: 'standard',\n  // required to lint *.vue files\n  plugins: [\n    'html'\n  ],\n  // add your custom rules here\n  'rules': {\n    // allow paren-less arrow functions\n    'arrow-parens': 0,\n    // allow async-await\n    'generator-star-spacing': 0,\n    // allow debugger during development\n    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,\n    \"no-irregular-whitespace\": [\"error\", {\n      \"skipComments\": true,\n      \"skipTemplates\": true\n    }],\n    \"no-unused-vars\": [\"warn\"]\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "package-lock.json\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\ndist/\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# editor\n.vscode\n.idea\n\n# test_code\ntest.vue\n\n# build\nvendor-manifest.json\nvendor.dll*.js\n\n"
  },
  {
    "path": ".postcssrc.js",
    "content": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n  \"plugins\": {\n    // to edit target browsers: use \"browserlist\" field in package.json\n    \"autoprefixer\": {}\n  }\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - 8.12.0\nsudo: required\nservices:\n  - docker\nenv:\n  global:\n    - CXX=g++-4.8\n  matrix:\n    - USE_SENTRY=1\n#    - USE_SENTRY=0\naddons:\n  apt:\n    sources:\n      - ubuntu-toolchain-r-test\n    packages:\n      - g++-4.8\nbefore_install:\n  - docker pull getsentry/sentry-cli\n\nscript:\n  - npm install\n  - npm run build:dll\n  - npm run build\n\nbefore_deploy:\n  - bash deploy/sentry_release.sh\n  - find dist/ -type f -name \"*.map\" -delete\n  - zip -r dist.zip dist\n\ndeploy:\n  provider: releases\n  skip_cleanup: true\n  api_key:\n    secure: I+BjkiPd+7XnMo1qWFhyz2xqElBLlGuCx1ZamvEa61zk4HPAnfWDPvpxXWihLdE4WDIkrOTRQ6Nh6GlVz+2uFugtdAlrxEWjU3bhadf5hAPNL2faNd52CN2qRNOuaWZmIkY4KUDmKoxvcFVQCKqMCxpghlNT7IJLwaC5wjogMeQKERXSa4rXl/ZGHdZzpkPo8SIEIy7hwQcHDl0ckm3t8wlXe6/xCRR2YoI3enZE15oJ15PChEcYbI6kUHNg3iIAddAAykqU0PnBtGSaPkl2JU90f4Rs62wW626wXyBs39ZU2rSbooyKF7OgFtS7KeTbYxyINBta8JH45plE/HVB3U3wy/8dBneZYr6ySGtSZSV10ortW59Al6Pifyo1na6tgkXSrckUOh1HFSiOsN6k46RXo6T1L1w1P8cUCJ2WYJksHJqBXnQmKbol9x3Gz6fQHR6yA5ToczKmg2ow549Q3g3oRb6RjRzXNkYu9sAZ2mhQ1fIjm7GkYPQvHzze+qwBxeX/ysFwIUNjUnK6u6EZunShz6fF9NEsDxzfUXPDOfB28MmxGkIi6TuT21F8tKfCG0C0CqLWBBve2agU9gutvIA1aY9i5K0YnlXPah1NYYzDPlk1RepWzJrq9VzGz3HxwY++cm6vR/7o9V+3WVpMIWHjnYwDTpK7pfm3VYbiLlg=\n  file: dist.zip\n  on:\n    repo: QingdaoU/OnlineJudgeFE\n    all_branches: true\n    tags: true\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017-present OnineJudge\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nThe MIT License (MIT)\n\nCopyright (c) 2013-present, Yuxi (Evan) You\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\nThe MIT License (MIT)\n\n\nCopyright (c) 2016-present iView\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nThe MIT License (MIT)\n\nCopyright (c) 2016 ElemeFE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# OnlineJudge Front End\n[![vue](https://img.shields.io/badge/vue-2.5.13-blue.svg?style=flat-square)](https://github.com/vuejs/vue)\n[![vuex](https://img.shields.io/badge/vuex-3.0.1-blue.svg?style=flat-square)](https://vuex.vuejs.org/)\n[![echarts](https://img.shields.io/badge/echarts-3.8.3-blue.svg?style=flat-square)](https://github.com/ecomfe/echarts)\n[![iview](https://img.shields.io/badge/iview-2.8.0-blue.svg?style=flat-square)](https://github.com/iview/iview)\n[![element-ui](https://img.shields.io/badge/element-2.0.9-blue.svg?style=flat-square)](https://github.com/ElemeFE/element)\n[![Build Status](https://travis-ci.org/QingdaoU/OnlineJudgeFE.svg?branch=master)](https://travis-ci.org/QingdaoU/OnlineJudgeFE)\n\n>### A multiple pages app built for OnlineJudge. [Demo](https://qduoj.com)\n\n## Features\n\n+ Webpack3 multiple pages with bundle size optimization\n+ Easy use simditor & Nice codemirror editor\n+ Amazing charting and visualization(echarts)\n+ User-friendly operation\n+ Quite beautiful：)\n\n## Get Started\n\nInstall nodejs **v8.12.0** first.\n\n### Linux\n\n```bash\nnpm install\n# we use webpack DllReference to decrease the build time,\n# this command only needs execute once unless you upgrade the package in build/webpack.dll.conf.js\nexport NODE_ENV=development \nnpm run build:dll\n\n# the dev-server will set proxy table to your backend\nexport TARGET=http://Your-backend\n\n# serve with hot reload at localhost:8080\nnpm run dev\n```\n### Windows\n\n```bash\nnpm install\n# we use webpack DllReference to decrease the build time,\n# this command only needs execute once unless you upgrade the package in build/webpack.dll.conf.js\nset NODE_ENV=development \nnpm run build:dll\n\n# the dev-server will set proxy table to your backend\nset TARGET=http://Your-backend\n\n# serve with hot reload at localhost:8080\nnpm run dev\n```\n\n## Screenshots\n\n[Check here.](https://github.com/QingdaoU/OnlineJudge)\n\n## Browser Support\n\nModern browsers and Internet Explorer 10+.\n\n## LICENSE\n\n[MIT](http://opensource.org/licenses/MIT)\n"
  },
  {
    "path": "build/build.js",
    "content": "'use strict'\nrequire('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nconst ora = require('ora')\nconst rm = require('rimraf')\nconst path = require('path')\nconst chalk = require('chalk')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst webpackConfig = require('./webpack.prod.conf')\n\nconst spinner = ora('building for production...')\nspinner.start()\n\nrm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {\n  if (err) throw err\n  webpack(webpackConfig, function (err, stats) {\n    spinner.stop()\n    if (err) throw err\n    process.stdout.write(stats.toString({\n      colors: true,\n      modules: false,\n      children: false,\n      chunks: false,\n      chunkModules: false\n    }) + '\\n\\n')\n\n    if (stats.hasErrors()) {\n      console.log(chalk.red('  Build failed with errors.\\n'))\n      process.exit(1)\n    }\n\n    console.log(chalk.cyan('  Congratulations, the project built complete without error\\n'))\n    console.log(chalk.yellow(\n      ' You can now check the onlinejudge in http://YouIP/'\n    ))\n  })\n})\n"
  },
  {
    "path": "build/check-versions.js",
    "content": "'use strict'\nconst chalk = require('chalk')\nconst semver = require('semver')\nconst packageConfig = require('../package.json')\nconst shell = require('shelljs')\nfunction exec (cmd) {\n  return require('child_process').execSync(cmd).toString().trim()\n}\n\nconst versionRequirements = [\n  {\n    name: 'node',\n    currentVersion: semver.clean(process.version),\n    versionRequirement: packageConfig.engines.node\n  }\n]\n\nif (shell.which('npm')) {\n  versionRequirements.push({\n    name: 'npm',\n    currentVersion: exec('npm --version'),\n    versionRequirement: packageConfig.engines.npm\n  })\n}\n\nmodule.exports = function () {\n  const warnings = []\n  for (let i = 0; i < versionRequirements.length; i++) {\n    const mod = versionRequirements[i]\n    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {\n      warnings.push(mod.name + ': ' +\n        chalk.red(mod.currentVersion) + ' should be ' +\n        chalk.green(mod.versionRequirement)\n      )\n    }\n  }\n\n  if (warnings.length) {\n    console.log('')\n    console.log(chalk.yellow('To use this template, you must update following to modules:'))\n    console.log()\n    for (let i = 0; i < warnings.length; i++) {\n      const warning = warnings[i]\n      console.log('  ' + warning)\n    }\n    console.log()\n    process.exit(1)\n  }\n}\n"
  },
  {
    "path": "build/dev-client.js",
    "content": "/* eslint-disable */\n'use strict'\nrequire('eventsource-polyfill')\nvar hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')\n\nhotClient.subscribe(function (event) {\n  if (event.action === 'reload') {\n    window.location.reload()\n  }\n})\n"
  },
  {
    "path": "build/dev-server.js",
    "content": "'use strict'\nrequire('./check-versions')()\n\nconst config = require('../config')\nif (!process.env.NODE_ENV) {\n  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)\n}\n\nconst opn = require('opn')\nconst path = require('path')\nconst express = require('express')\nconst webpack = require('webpack')\nconst proxyMiddleware = require('http-proxy-middleware')\nconst webpackConfig = require('./webpack.dev.conf')\n\n// default port where dev server listens for incoming traffic\nconst port = process.env.PORT || config.dev.port\n// automatically open browser, if not set will be false\nconst autoOpenBrowser = !!config.dev.autoOpenBrowser\n// Define HTTP proxies to your custom API backend\n// https://github.com/chimurai/http-proxy-middleware\nconst proxyTable = config.dev.proxyTable\n\nconst app = express()\nconst compiler = webpack(webpackConfig)\n\nconst devMiddleware = require('webpack-dev-middleware')(compiler, {\n  publicPath: webpackConfig.output.publicPath,\n  quiet: true\n})\n\nconst hotMiddleware = require('webpack-hot-middleware')(compiler, {\n  log: false,\n  heartbeat: 2000\n})\n// force page reload when html-webpack-plugin template changes\n// currently disabled until this is resolved:\n// https://github.com/jantimon/html-webpack-plugin/issues/680\n// compiler.plugin('compilation', function (compilation) {\n//   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {\n//     hotMiddleware.publish({ action: 'reload' })\n//     cb()\n//   })\n// })\n\n// enable hot-reload and state-preserving\n// compilation error display\napp.use(hotMiddleware)\n\n// proxy api requests\nObject.keys(proxyTable).forEach(function (context) {\n  let options = proxyTable[context]\n  if (typeof options === 'string') {\n    options = { target: options }\n  }\n  app.use(proxyMiddleware(options.filter || context, options))\n})\n\n// handle fallback for HTML5 history API\nconst rewrites = {\n  rewrites: [{\n    from: '/admin/', // 正则或者字符串\n    to: '/admin/index.html', // 字符串或者函数\n  }]\n}\nconst historyMiddleware = require('connect-history-api-fallback')(rewrites);\napp.use(historyMiddleware)\n\n// serve webpack bundle output\napp.use(devMiddleware)\n\n// serve pure static assets\nconst staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)\napp.use(staticPath, express.static('./static'))\n\nconst uri = 'http://localhost:' + port\n\nvar _resolve\nvar _reject\nvar readyPromise = new Promise((resolve, reject) => {\n  _resolve = resolve\n  _reject = reject\n})\n\nvar server\nvar portfinder = require('portfinder')\nportfinder.basePort = port\n\nconsole.log('> Starting dev server...')\ndevMiddleware.waitUntilValid(() => {\n  portfinder.getPort((err, port) => {\n    if (err) {\n      _reject(err)\n    }\n    process.env.PORT = port\n    var uri = 'http://localhost:' + port\n    console.log('> Listening at ' + uri + '\\n')\n    // when env is testing, don't need open it\n    if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {\n      opn(uri)\n    }\n    server = app.listen(port)\n    _resolve()\n  })\n})\n\nmodule.exports = {\n  ready: readyPromise,\n  close: () => {\n    server.close()\n  }\n}\n"
  },
  {
    "path": "build/utils.js",
    "content": "'use strict'\nconst path = require('path')\nconst config = require('../config')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\n\nexports.assetsPath = function (_path) {\n  const assetsSubDirectory = process.env.NODE_ENV === 'production'\n    ? config.build.assetsSubDirectory\n    : config.dev.assetsSubDirectory\n  return path.posix.join(assetsSubDirectory, _path)\n}\n\nexports.cssLoaders = function (options) {\n  options = options || {}\n\n  const cssLoader = {\n    loader: 'css-loader',\n    options: {\n      minimize: process.env.NODE_ENV === 'production',\n      sourceMap: options.sourceMap\n    }\n  }\n\n  // generate loader string to be used with extract text plugin\n  function generateLoaders (loader, loaderOptions) {\n    const loaders = [cssLoader]\n    if (loader) {\n      loaders.push({\n        loader: loader + '-loader',\n        options: Object.assign({}, loaderOptions, {\n          sourceMap: options.sourceMap\n        })\n      })\n    }\n\n    // Extract CSS when that option is specified\n    // (which is the case during production build)\n    if (options.extract) {\n      return ExtractTextPlugin.extract({\n        use: loaders,\n        fallback: 'vue-style-loader'\n      })\n    } else {\n      return ['vue-style-loader'].concat(loaders)\n    }\n  }\n\n  // https://vue-loader.vuejs.org/en/configurations/extract-css.html\n  return {\n    css: generateLoaders(),\n    postcss: generateLoaders(),\n    less: generateLoaders('less'),\n    sass: generateLoaders('sass', {indentedSyntax: true}),\n    scss: generateLoaders('sass'),\n    stylus: generateLoaders('stylus'),\n    styl: generateLoaders('stylus')\n  }\n}\n\n// Generate loaders for standalone style files (outside of .vue)\nexports.styleLoaders = function (options) {\n  const output = []\n  const loaders = exports.cssLoaders(options)\n  for (const extension in loaders) {\n    const loader = loaders[extension]\n    output.push({\n      test: new RegExp('\\\\.' + extension + '$'),\n      use: loader\n    })\n  }\n  return output\n}\n\nexports.getNodeEnv = function () {\n  const NODE_ENV = process.env.NODE_ENV\n  return NODE_ENV ? NODE_ENV: 'production'\n}\n"
  },
  {
    "path": "build/vue-loader.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst config = require('../config')\nconst isProduction = process.env.NODE_ENV === 'production'\n\nmodule.exports = {\n  loaders: utils.cssLoaders({\n    sourceMap: isProduction\n      ? config.build.productionSourceMap\n      : config.dev.cssSourceMap,\n    extract: isProduction\n  }),\n  transformToRequire: {\n    video: 'src',\n    source: 'src',\n    img: 'src',\n    image: 'xlink:href'\n  }\n}\n"
  },
  {
    "path": "build/webpack.base.conf.js",
    "content": "'use strict'\nconst path = require('path')\nconst glob = require('glob')\nconst webpack = require('webpack')\nconst utils = require('./utils')\nconst config = require('../config')\nconst vueLoaderConfig = require('./vue-loader.conf')\nconst HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin')\n\nfunction resolve (dir) {\n  return path.join(__dirname, '..', dir)\n}\n\nfunction getEntries () {\n  const base = {\n    'oj': ['./src/pages/oj/index.js'],\n    'admin': ['./src/pages/admin/index.js']\n  }\n  if (process.env.USE_SENTRY === '1') {\n    Object.keys(base).forEach(entry => {\n      base[entry].push('./src/utils/sentry.js')\n    })\n  }\n  return base\n}\n\n// get all entries\nconst entries = getEntries()\nconsole.log(\"All entries: \")\nObject.keys(entries).forEach(entry => {\n  console.log(entry)\n  entries[entry].forEach(ele => {\n    console.log(\"- %s\", ele)\n  })\n  console.log()\n})\n\n// prepare vendor asserts\nconst globOptions = {cwd: resolve('static/js')};\nlet vendorAssets = glob.sync('vendor.dll.*.js', globOptions);\nvendorAssets = vendorAssets.map(file => 'static/js/' + file)\n\n\nmodule.exports = {\n  entry: entries,\n  output: {\n    path: config.build.assetsRoot,\n    filename: '[name].js',\n    publicPath: process.env.NODE_ENV === 'production'\n      ? config.build.assetsPublicPath\n      : config.dev.assetsPublicPath\n  },\n  resolve: {\n    modules: ['node_modules'],\n    extensions: ['.js', '.vue', '.json'],\n    alias: {\n      'vue$': 'vue/dist/vue.esm.js',\n      '@': resolve('src'),\n      '@oj': resolve('src/pages/oj'),\n      '@admin': resolve('src/pages/admin'),\n      '~': resolve('src/components')\n    }\n  },\n  module: {\n    rules: [\n      // {\n      //   test: /\\.(js|vue)$/,\n      //   loader: 'eslint-loader',\n      //   enforce: 'pre',\n      //   include: [resolve('src')],\n      //   options: {\n      //     formatter: require('eslint-friendly-formatter')\n      //   }\n      // },\n      {\n        test: /\\.vue$/,\n        loader: 'vue-loader',\n        options: vueLoaderConfig\n      },\n      {\n        test: /\\.js$/,\n        loader: 'babel-loader?cacheDirectory=true',\n        exclude: /node_modules/,\n        include: [resolve('src'), resolve('test')]\n      },\n      {\n        test: /\\.(png|jpe?g|gif|svg)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('img/[name].[hash:7].[ext]')\n        }\n      },\n      {\n        test: /\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('media/[name].[hash:7].[ext]')\n        }\n      },\n      {\n        test: /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/,\n        loader: 'url-loader',\n        options: {\n          limit: 10000,\n          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')\n        }\n      }\n    ]\n  },\n  plugins: [\n    new webpack.DllReferencePlugin({\n      context: __dirname,\n      manifest: require('./vendor-manifest.json')\n    }),\n    new HtmlWebpackIncludeAssetsPlugin({\n      assets: [vendorAssets[0]],\n      files: ['index.html', 'admin/index.html'],\n      append: false\n    }),\n  ]\n}\n"
  },
  {
    "path": "build/webpack.dev.conf.js",
    "content": "'use strict'\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')\n\n// add hot-reload related code to entry chunks\nObject.keys(baseWebpackConfig.entry).forEach(function (name) {\n  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])\n})\n\nmodule.exports = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })\n  },\n  // cheap-module-eval-source-map is faster for development\n  devtool: '#cheap-module-eval-source-map',\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': config.dev.env\n    }),\n    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage\n    new webpack.HotModuleReplacementPlugin(),\n    new webpack.NoEmitOnErrorsPlugin(),\n\n    // https://github.com/ampedandwired/html-webpack-plugin\n    new HtmlWebpackPlugin({\n      filename: config.build.ojIndex,\n      template: config.build.ojTemplate,\n      chunks: ['oj'],\n      inject: true\n    }),\n    new HtmlWebpackPlugin({\n      filename: config.build.adminIndex,\n      template: config.build.adminTemplate,\n      chunks: ['admin'],\n      inject: true\n    }),\n    new FriendlyErrorsPlugin()\n  ]\n})\n"
  },
  {
    "path": "build/webpack.dll.conf.js",
    "content": "const webpack = require('webpack');\nconst path = require('path');\nconst UglifyJSPlugin = require('uglifyjs-webpack-plugin')\nconst config = require('../config')\nconst utils = require('./utils')\nconst glob = require('glob')\nconst fs = require('fs')\n\nfunction resolve (dir) {\n  return path.join(__dirname, '..', dir)\n}\n\nconst NODE_ENV = utils.getNodeEnv()\n\nconst vendors = [\n  'vue/dist/vue.esm.js',\n  'vue-router',\n  'vuex',\n  'axios',\n  'moment',\n  'raven-js',\n  'browser-detect'\n];\n\n// clear old dll\nconst globOptions = {cwd: resolve('static/js'), absolute: true};\nlet oldDlls = glob.sync('vendor.dll.*.js', globOptions);\nconsole.log(\"cleaning old dll..\")\noldDlls.forEach(f => {\n  fs.unlink(f, _ => {})\n})\nconsole.log(\"building ..\")\n\nmodule.exports = {\n  entry: {\n    \"vendor\": vendors,\n  },\n  output: {\n    path: path.join(__dirname, '../static/js'),\n    filename: '[name].dll.[hash:7].js',\n    library: '[name]_[hash]_dll',\n  },\n  plugins: [\n    new webpack.DefinePlugin({\n      'process.env': NODE_ENV === 'production' ? config.build.env : config.dev.env\n    }),\n    new webpack.optimize.ModuleConcatenationPlugin(),\n    new webpack.ContextReplacementPlugin(/moment[\\/\\\\]locale$/, /zh-cn/),\n    new UglifyJSPlugin({\n      exclude: /\\.min\\.js$/,\n      cache: true,\n      parallel: true\n    }),\n    new webpack.DllPlugin({\n      context: __dirname,\n      path: path.join(__dirname, '[name]-manifest.json'),\n      name: '[name]_[hash]_dll',\n    })\n  ]\n};\n"
  },
  {
    "path": "build/webpack.prod.conf.js",
    "content": "'use strict'\nconst os = require('os');\nconst path = require('path')\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\nconst merge = require('webpack-merge')\nconst baseWebpackConfig = require('./webpack.base.conf')\nconst CopyWebpackPlugin = require('copy-webpack-plugin')\nconst HtmlWebpackPlugin = require('html-webpack-plugin')\nconst ExtractTextPlugin = require('extract-text-webpack-plugin')\nconst OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')\nconst UglifyJSPlugin = require('uglifyjs-webpack-plugin')\n\nconst webpackConfig = merge(baseWebpackConfig, {\n  module: {\n    rules: utils.styleLoaders({\n      sourceMap: config.build.productionSourceMap,\n      extract: true\n    })\n  },\n  devtool: config.build.productionSourceMap ? '#hidden-source-map' : false,\n  output: {\n    path: config.build.assetsRoot,\n    filename: utils.assetsPath('js/[name].[chunkhash].js'),\n    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')\n  },\n  plugins: [\n    // http://vuejs.github.io/vue-loader/en/workflow/production.html\n    new webpack.DefinePlugin({\n      'process.env': config.build.env\n    }),\n    new webpack.optimize.ModuleConcatenationPlugin(),\n\n    // extract css into its own file\n    new ExtractTextPlugin({\n      filename: utils.assetsPath('css/[name].[contenthash].css'),\n      allChunks: true\n    }),\n    // Compress extracted CSS. We are using this plugin so that possible\n    // duplicated CSS from different components can be deduped.\n    new OptimizeCSSPlugin({\n      cssProcessorOptions: {\n        safe: true\n      }\n    }),\n    new UglifyJSPlugin({\n      exclude: /\\.min\\.js$/,\n      cache: true,\n      parallel: true,\n      sourceMap: true\n    }),\n\n    // keep module.id stable when vender modules does not change\n    new webpack.HashedModuleIdsPlugin(),\n    // split vendor js into its own file\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'vendor',\n      chunks: ['oj', 'admin'],\n      minChunks: 2\n      // minChunks: function (module) {\n      // any required modules inside node_modules are extracted to vendor\n      // return (\n      //   module.resource &&\n      //   /\\.js$/.test(module.resource) &&\n      //   module.resource.indexOf(\n      //     path.join(__dirname, '../node_modules')\n      //   ) === 0\n      // )\n      // }\n    }),\n    // extract webpack runtime and module manifest to its own file in order to\n    // prevent vendor hash from being updated whenever app bundle is updated\n    new webpack.optimize.CommonsChunkPlugin({\n      name: 'manifest',\n      chunks: ['vendor']\n    }),\n    // copy custom static assets\n    new CopyWebpackPlugin([\n      {\n        from: path.resolve(__dirname, '../static'),\n        to: config.build.assetsSubDirectory,\n        ignore: ['.*']\n      }\n    ]),\n    // generate dist index.html with correct asset hash for caching.\n    // you can customize output by editing /index.html\n    // see https://github.com/ampedandwired/html-webpack-plugin\n    // oj\n    new HtmlWebpackPlugin({\n      filename: config.build.ojIndex,\n      template: config.build.ojTemplate,\n      chunks: ['manifest', 'vendor', 'oj'],\n      inject: true,\n      minify: {\n        removeComments: true,\n        collapseWhitespace: true,\n        removeAttributeQuotes: true\n        // more options:\n        // https://github.com/kangax/html-minifier#options-quick-reference\n      }\n    }),\n    // admin\n    new HtmlWebpackPlugin({\n      filename: config.build.adminIndex,\n      template: config.build.adminTemplate,\n      chunks: ['manifest', 'vendor', 'admin'],\n      inject: true,\n      minify: {\n        removeComments: true,\n        collapseWhitespace: true,\n        removeAttributeQuotes: true\n        // more options:\n        // https://github.com/kangax/html-minifier#options-quick-reference\n      }\n    })\n  ]\n})\n\nif (config.build.productionGzip) {\n  const CompressionWebpackPlugin = require('compression-webpack-plugin')\n\n  webpackConfig.plugins.push(\n    new CompressionWebpackPlugin({\n      asset: '[path].gz[query]',\n      algorithm: 'gzip',\n      test: new RegExp(\n        '\\\\.(' +\n        config.build.productionGzipExtensions.join('|') +\n        ')$'\n      ),\n      threshold: 10240,\n      minRatio: 0.8\n    })\n  )\n}\n\nif (config.build.bundleAnalyzerReport) {\n  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin\n  webpackConfig.plugins.push(new BundleAnalyzerPlugin())\n}\n\nmodule.exports = webpackConfig\n"
  },
  {
    "path": "config/dev.env.js",
    "content": "let date = require('moment')().format('YYYYMMDD')\nlet commit = require('child_process').execSync('git rev-parse HEAD').toString().slice(0, 5)\nlet version = `\"${date}-${commit}\"`\n\nconsole.log(`current version is ${version}`)\n\nmodule.exports = {\n  NODE_ENV: '\"development\"',\n  VERSION: version,\n  USE_SENTRY: '0'\n}\n"
  },
  {
    "path": "config/index.js",
    "content": "'use strict'\n// Template version: 1.1.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path = require('path')\nconst commonProxy = {\n  onProxyReq: (proxyReq, req, res) => {\n    proxyReq.setHeader('Referer', process.env.TARGET)\n  },\n  target: process.env.TARGET,\n  changeOrigin: true\n}\n\nmodule.exports = {\n  build: {\n    env: require('./prod.env'),\n    ojIndex: path.resolve(__dirname, '../dist/index.html'),\n    ojTemplate: path.resolve(__dirname, '../src/pages/oj/index.html'),\n    adminIndex: path.resolve(__dirname, '../dist/admin/index.html'),\n    adminTemplate: path.resolve(__dirname, '../src/pages/admin/index.html'),\n    assetsRoot: path.resolve(__dirname, '../dist'),\n    assetsSubDirectory: 'static',\n    assetsPublicPath: '/__STATIC_CDN_HOST__/',\n    productionSourceMap: process.env.USE_SENTRY === '1',\n    // Gzip off by default as many popular static hosts such as\n    // Surge or Netlify already gzip all static assets for you.\n    // Before setting to `true`, make sure to:\n    // npm install --save-dev compression-webpack-plugin\n    productionGzip: false,\n    productionGzipExtensions: ['js', 'css'],\n    // Run the build command with an extra argument to\n    // View the bundle analyzer report after build finishes:\n    // `npm run build --report`\n    // Set to `true` or `false` to always turn it on or off\n    bundleAnalyzerReport: process.env.npm_config_report\n  },\n  dev: {\n    env: require('./dev.env'),\n    port: process.env.PORT || 8080,\n    autoOpenBrowser: true,\n    assetsSubDirectory: 'static',\n    assetsPublicPath: '/',\n    proxyTable: {\n      \"/api\": commonProxy,\n      \"/public\": commonProxy\n    },\n    // CSS Sourcemaps off by default because relative paths are \"buggy\"\n    // with this option, according to the CSS-Loader README\n    // (https://github.com/webpack/css-loader#sourcemaps)\n    // In our experience, they generally work as expected,\n    // just be aware of this issue when enabling this option.\n    cssSourceMap: false\n  }\n}\n"
  },
  {
    "path": "config/prod.env.js",
    "content": "const merge = require('webpack-merge')\nconst devEnv = require('./dev.env')\n\nmodule.exports = merge(devEnv, {\n  NODE_ENV: '\"production\"',\n})\n"
  },
  {
    "path": "deploy/Dockerfile",
    "content": "FROM node:6.11-alpine\n\nRUN apk add --no-cache nginx git python build-base\n\nVOLUME [ \"/OJ_FE\", \"/var/log/nginx/\", \"/data/avatar\"]\nEXPOSE 80\n\nCMD [\"/bin/sh\", \"/OJ_FE/deploy/run.sh\"]\n"
  },
  {
    "path": "deploy/nginx.conf",
    "content": "user nginx;\n\n# Set number of worker processes automatically based on number of CPU cores.\nworker_processes auto;\n\n# Enables the use of JIT for regular expressions to speed-up their processing.\npcre_jit on;\n\n# Configures default error logger.\nerror_log /var/log/nginx/nginx_error.log warn;\n\ndaemon off;\n\n# set pid path\npid  /tmp/nginx.pid;\n\n# Includes files with directives to load dynamic modules.\ninclude /etc/nginx/modules/*.conf;\n\n\nevents {\n\t# The maximum number of simultaneous connections that can be opened by\n\t# a worker process.\n\tworker_connections 1024;\n}\n\nhttp {\n\t# Includes mapping of file name extensions to MIME types of responses\n\t# and defines the default type.\n\tinclude /etc/nginx/mime.types;\n\tdefault_type application/octet-stream;\n\n\t# Name servers used to resolve names of upstream servers into addresses.\n\t# It's also needed when using tcpsocket and udpsocket in Lua modules.\n\t#resolver 208.67.222.222 208.67.220.220;\n\n\t# Don't tell nginx version to clients.\n\tserver_tokens off;\n\n\t# Specifies the maximum accepted body size of a client request, as\n\t# indicated by the request header Content-Length. If the stated content\n\t# length is greater than this size, then the client receives the HTTP\n\t# error code 413. Set to 0 to disable.\n\tclient_max_body_size 100m;\n\n\t# Timeout for keep-alive connections. Server will close connections after\n\t# this time.\n\tkeepalive_timeout 10;\n\n\t# Sendfile copies data between one FD and other from within the kernel,\n\t# which is more efficient than read() + write().\n\tsendfile on;\n\n\t# Don't buffer data-sends (disable Nagle algorithm).\n\t# Good for sending frequent small bursts of data in real time.\n\ttcp_nodelay on;\n\n\t# Causes nginx to attempt to send its HTTP response head in one packet,\n\t# instead of using partial frames.\n\t#tcp_nopush on;\n\n\n\t# Path of the file with Diffie-Hellman parameters for EDH ciphers.\n\t#ssl_dhparam /etc/ssl/nginx/dh2048.pem;\n\n\t# Specifies that our cipher suits should be preferred over client ciphers.\n\tssl_prefer_server_ciphers on;\n\n\t# Enables a shared SSL cache with size that can hold around 8000 sessions.\n\tssl_session_cache shared:SSL:2m;\n\n\n\t# Enable gzipping of responses.\n\tgzip on;\n\tgzip_types application/javascript text/css;\n\n\t# Set the Vary HTTP header as defined in the RFC 2616.\n\tgzip_vary on;\n\n\t# Enable checking the existence of precompressed files.\n\t#gzip_static on;\n\n\n\t# Specifies the main log format.\n\tlog_format main '$remote_addr - $remote_user [$time_local] \"$request\" '\n\t\t\t'$status $body_bytes_sent \"$http_referer\" '\n\t\t\t'\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n\t# Sets the path, format, and configuration for a buffered log write.\n\t# access_log /var/log/nginx/access.log main;\n\taccess_log off;\n\n\tserver {\n        listen 80 default_server;\n        server_name _;\n\n        location /public {\n            root /app/data;\n        }\n        location /api {\n            proxy_pass http://oj-backend:8080;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header Host $http_host;\n            client_max_body_size 200M;\n        }\n        location /admin {\n            root /app/dist/admin;\n            try_files $uri $uri/ /index.html =404;\n        }\n        location / {\n            root /app/dist;\n            try_files $uri $uri/ /index.html =404;\n        }\n    }\n}\n"
  },
  {
    "path": "deploy/run.sh",
    "content": "#!/bin/sh\n\nbase=/OJ_FE\n\nbuild_vendor_dll()\n{\n  if [ ! -e \"${base}/build/vendor-manifest.json\" ]\n  then\n      npm run build:dll\n  fi\n}\ncd $base\nnpm install --registry=https://registry.npm.taobao.org && \\\nbuild_vendor_dll && \\\nnpm run build\n\nif [ $? -ne 0 ]; then\n    echo \"Build error, please check node version and package.json\"\n    exit 1\nfi\n\nexec nginx -c /OJ_FE/deploy/nginx.conf\n"
  },
  {
    "path": "deploy/sentry_release.sh",
    "content": "#!/bin/bash\n\nDATE=`date +%Y%m%d`\nCOMMIT=`git rev-parse HEAD`\nVERSION=\"$DATE-${COMMIT:0:5}\"\n\necho \"Current version is $VERSION\"\n\nif [ ! -z $USE_SENTRY ] && [ $USE_SENTRY == '1' ]; then\n\n# create new release according to `VERSION`\ndocker run --rm -it -v $(pwd):/work getsentry/sentry-cli \\\n   sentry-cli --auth-token $SENTRY_AUTH_TOKEN releases -o onlinejudge -p onlinejudgefe new $VERSION\n\n# upload js and source_maps\ndocker run --rm -it -v $(pwd):/work getsentry/sentry-cli \\\n   sentry-cli --auth-token $SENTRY_AUTH_TOKEN releases -o onlinejudge -p onlinejudgefe files $VERSION upload-sourcemaps ./dist/static/js\nfi\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"onlinejudge\",\n  \"version\": \"2.7.6\",\n  \"description\": \"onlinejudge front end\",\n  \"author\": \"zemal <rawidn@163.com>\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"node build/dev-server.js\",\n    \"start\": \"npm run dev\",\n    \"build\": \"node build/build.js\",\n    \"build:dll\": \"webpack --config=build/webpack.dll.conf.js\",\n    \"lint\": \"eslint --ext .js,.vue src\"\n  },\n  \"dependencies\": {\n    \"autoprefixer\": \"^7.1.2\",\n    \"axios\": \"^0.18.0\",\n    \"babel-core\": \"^6.26.3\",\n    \"babel-loader\": \"^7.1.4\",\n    \"babel-plugin-syntax-dynamic-import\": \"^6.18.0\",\n    \"babel-plugin-transform-runtime\": \"^6.22.0\",\n    \"babel-polyfill\": \"^6.26.0\",\n    \"babel-preset-env\": \"^1.7.0\",\n    \"babel-preset-stage-2\": \"^6.22.0\",\n    \"browser-detect\": \"^0.2.27\",\n    \"chalk\": \"^2.4.1\",\n    \"copy-webpack-plugin\": \"^4.5.1\",\n    \"css-loader\": \"^0.28.11\",\n    \"echarts\": \"^3.8.5\",\n    \"element-ui\": \"^2.3.7\",\n    \"extract-text-webpack-plugin\": \"^3.0.0\",\n    \"file-loader\": \"^1.1.11\",\n    \"font-awesome\": \"^4.7.0\",\n    \"glob\": \"^7.1.2\",\n    \"highlight.js\": \"^9.12.0\",\n    \"html-webpack-include-assets-plugin\": \"^1.0.4\",\n    \"html-webpack-plugin\": \"^2.30.1\",\n    \"iview\": \"^2.13.0\",\n    \"katex\": \"^0.10.0\",\n    \"less\": \"^3.8.1\",\n    \"less-loader\": \"^4.1.0\",\n    \"moment\": \"^2.22.1\",\n    \"optimize-css-assets-webpack-plugin\": \"^3.2.0\",\n    \"ora\": \"^1.2.0\",\n    \"papaparse\": \"^4.4.0\",\n    \"raven-js\": \"^3.25.0\",\n    \"screenfull\": \"^3.3.2\",\n    \"shelljs\": \"^0.8.2\",\n    \"tar-simditor\": \"^3.0.5\",\n    \"tar-simditor-markdown\": \"^1.2.3\",\n    \"uglifyjs-webpack-plugin\": \"^1.2.5\",\n    \"url-loader\": \"^0.5.8\",\n    \"vue\": \"^2.5.16\",\n    \"vue-analytics\": \"^5.10.4\",\n    \"vue-clipboard2\": \"^0.2.1\",\n    \"vue-codemirror-lite\": \"^1.0.4\",\n    \"vue-cropper\": \"^0.4.6\",\n    \"vue-echarts\": \"^2.6.0\",\n    \"vue-i18n\": \"^7.7.0\",\n    \"vue-loader\": \"^13.3.0\",\n    \"vue-router\": \"^3.0.1\",\n    \"vue-style-loader\": \"^3.0.1\",\n    \"vue-template-compiler\": \"^2.5.16\",\n    \"vuex\": \"^3.0.1\",\n    \"vuex-router-sync\": \"^5.0.0\",\n    \"webpack\": \"^3.6.0\",\n    \"webpack-merge\": \"^4.1.2\"\n  },\n  \"devDependencies\": {\n    \"babel-eslint\": \"^7.1.1\",\n    \"babel-register\": \"^6.22.0\",\n    \"connect-history-api-fallback\": \"^1.3.0\",\n    \"eslint\": \"^3.19.0\",\n    \"eslint-config-standard\": \"^10.2.1\",\n    \"eslint-friendly-formatter\": \"^3.0.0\",\n    \"eslint-loader\": \"^1.7.1\",\n    \"eslint-plugin-html\": \"^3.0.0\",\n    \"eslint-plugin-import\": \"^2.11.0\",\n    \"eslint-plugin-node\": \"^5.2.0\",\n    \"eslint-plugin-promise\": \"^3.7.0\",\n    \"eslint-plugin-standard\": \"^3.1.0\",\n    \"eventsource-polyfill\": \"^0.9.6\",\n    \"express\": \"^4.16.3\",\n    \"friendly-errors-webpack-plugin\": \"^1.7.0\",\n    \"http-proxy-middleware\": \"^0.18.0\",\n    \"opn\": \"^5.3.0\",\n    \"portfinder\": \"^1.0.13\",\n    \"rimraf\": \"^2.6.0\",\n    \"semver\": \"^5.5.0\",\n    \"webpack-bundle-analyzer\": \"^2.11.2\",\n    \"webpack-dev-middleware\": \"^1.12.0\",\n    \"webpack-hot-middleware\": \"^2.22.1\"\n  },\n  \"engines\": {\n    \"node\": \">= 4.0.0\",\n    \"npm\": \">= 3.0.0\"\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not ie <= 8\"\n  ]\n}\n"
  },
  {
    "path": "src/i18n/admin/en-US.js",
    "content": "export const m = {\n  // SideMenu.vue\n  Dashboard: 'Dashboard',\n  General: 'General',\n  User: 'User',\n  Announcement: 'Announcement',\n  System_Config: 'System Config',\n  Judge_Server: 'Judge Server',\n  Prune_Test_Case: 'Prune Test Case',\n  Problem: 'Problem',\n  FromFile: 'From File',\n  ToFile: 'To File',\n  ShareSubmission: 'Share Submission',\n  Problem_List: 'Problem List',\n  Create_Problem: 'Create Problem',\n  Export_Import_Problem: 'Export Or Import Problem',\n  Contest: 'Contest',\n  Contest_List: 'Contest List',\n  Create_Contest: 'Create Contest',\n  // User.vue\n  User_User: 'User',\n  Import_User: 'Import User',\n  Generate_User: 'Generate User',\n  // User.vue-dialog\n  User_Info: 'User',\n  User_Username: 'Username',\n  User_Real_Name: 'Real Name',\n  User_Email: 'Email',\n  User_New_Password: 'New Password',\n  User_Type: 'User Type',\n  Problem_Permission: 'Problem Permission',\n  Two_Factor_Auth: 'Two Factor Auth',\n  Is_Disabled: 'Is Disabled',\n  // Announcement.vue\n  General_Announcement: 'Announcement',\n  Announcement_Title: 'Title',\n  Announcement_Content: 'Content',\n  Announcement_visible: 'Visible',\n  // Conf.vue\n  SMTP_Config: 'SMTP Config',\n  Server: 'Server',\n  Port: 'Port',\n  Email: 'Email',\n  Password: 'Password',\n  Website_Config: 'Web Config',\n  Base_Url: 'Base Url',\n  Name: 'Name',\n  Shortcut: 'Shortcut',\n  Footer: 'Footer',\n  Allow_Register: 'Allow Register',\n  Submission_List_Show_All: 'Submission List Show All',\n  // JudgeServer.vue\n  Judge_Server_Token: 'Judge Server Token',\n  Judge_Server_Info: 'Judge Server',\n  IP: 'IP',\n  Judger_Version: 'Judger Version',\n  Service_URL: 'Service URL',\n  Last_Heartbeat: 'Last Heartbeat',\n  Create_Time: 'Create Time',\n  // PruneTestCase\n  Test_Case_Prune_Test_Case: 'Prune Test Case',\n  // Problem.vue\n  Display_ID: 'Display ID',\n  Title: 'Title',\n  Description: 'Description',\n  Input_Description: 'Input Description',\n  Output_Description: 'Output Description',\n  Time_Limit: 'Time Limit',\n  Memory_limit: 'Memory limit',\n  Difficulty: 'Difficulty',\n  Visible: 'Visible',\n  Languages: 'Languages',\n  Input_Samples: 'Input Samples',\n  Output_Samples: 'Output Samples',\n  Add_Sample: 'Add Sample',\n  Code_Template: 'Code_Template',\n  Special_Judge: 'Special Judge',\n  Use_Special_Judge: 'Use Special Judge',\n  Special_Judge_Code: 'Special Judge Code',\n  SPJ_language: 'SPJ language',\n  Compile: 'Compile',\n  TestCase: 'TestCase',\n  IOMode: 'IO Mode',\n  InputFileName: 'Input File Name',\n  OutputFileName: 'Output File Name',\n  Type: 'Type',\n  Input: 'Input',\n  Output: 'Output',\n  Score: 'Score',\n  Hint: 'Hint',\n  Source: 'Source',\n  Edit_Problem: 'Edit Problme',\n  Add_Problme: 'Add Problem',\n  High: 'High',\n  Mid: 'Mid',\n  Low: 'Low',\n  Tag: 'Tag',\n  New_Tag: 'New Tag',\n   // ProblemList.vue\n  Contest_Problem_List: 'Contest Problem List',\n  // Contest.vue\n  ContestTitle: 'Title',\n  ContestDescription: 'Description',\n  Contest_Start_Time: 'Start Time',\n  Contest_End_Time: 'End Time',\n  Contest_Password: 'Password',\n  Contest_Rule_Type: 'Contest Rule Type',\n  Real_Time_Rank: 'Real Time Rank',\n  Contest_Status: 'Status',\n  Allowed_IP_Ranges: 'Allowed IP Ranges',\n  CIDR_Network: 'CIDR Network',\n  // Dashboard.vue\n  Last_Login: 'Last Login',\n  System_Overview: 'System Overview',\n  DashBoardJudge_Server: 'Judge Server',\n  HTTPS_Status: 'HTTPS Status',\n  Force_HTTPS: 'Force HTTPS',\n  CDN_HOST: 'CDN HOST',\n  // Login.vue\n  Welcome_to_Login: 'Welcome to Login',\n  GO: 'GO',\n  username: 'username',\n  password: 'password'\n}\n"
  },
  {
    "path": "src/i18n/admin/zh-CN.js",
    "content": "export const m = {\n  // SideMenu.vue\n  Dashboard: '仪表盘',\n  General: '常用设置',\n  User: '用户管理',\n  Announcement: '公告管理',\n  System_Config: '系统配置',\n  Judge_Server: '判题服务器',\n  Prune_Test_Case: '测试用例',\n  Problem: '问题',\n  FromFile: '读取文件',\n  ToFile: '写入文件',\n  ShareSubmission: '分享提交',\n  Problem_List: '问题列表',\n  Create_Problem: '增加题目',\n  Export_Import_Problem: '导入导出题目',\n  Contest: '比赛&练习',\n  Contest_List: '比赛列表',\n  Create_Contest: '创建比赛',\n  // User.vue\n  User_User: '用户',\n  Import_User: '导入用户',\n  Generate_User: '生成用户',\n  // User.vue-dialog\n  User_Info: '用户信息',\n  User_Username: '用户名',\n  User_Real_Name: '真实姓名',\n  User_Email: '用户邮箱',\n  User_New_Password: '用户密码',\n  User_Type: '用户类型',\n  Problem_Permission: '问题权限',\n  Two_Factor_Auth: '双因素认证',\n  Is_Disabled: '是否禁用',\n  // Announcement.vue\n  General_Announcement: '公告',\n  Announcement_Title: '标题',\n  Announcement_Content: '内容',\n  Announcement_visible: '是否可见',\n  // Conf.vue\n  SMTP_Config: 'SMTP 设置',\n  Server: '服务器',\n  Port: '端口',\n  Email: '邮箱',\n  Password: '授权码',\n  Website_Config: '网站设置',\n  Base_Url: '基础 Url',\n  Name: '名称',\n  Shortcut: '简称',\n  Footer: '页脚',\n  Allow_Register: '是否允许注册',\n  Submission_List_Show_All: '显示全部题目的提交',\n  // JudgeServer.vue\n  Judge_Server_Token: '判题服务器接口',\n  Judge_Server_Info: '判题服务器',\n  IP: 'IP',\n  Judger_Version: '判题机版本',\n  Service_URL: '服务器 URL',\n  Last_Heartbeat: '上一次心跳',\n  Create_Time: '创建时间',\n  // PruneTestCase\n  Test_Case_Prune_Test_Case: '精简测试用例',\n  // Problem.vue\n  Display_ID: '显示 ID',\n  Title: '题目',\n  Description: '描述',\n  Input_Description: '输入描述',\n  Output_Description: '输出描述',\n  Time_Limit: '时间限制',\n  Memory_limit: '内存限制',\n  Difficulty: '难度',\n  Visible: '是否可见',\n  Languages: '可选编程语言',\n  Input_Samples: '输入样例',\n  Output_Samples: '输出样例',\n  Add_Sample: '添加样例',\n  Code_Template: '代码模板',\n  Special_Judge: 'Special Judge',\n  Use_Special_Judge: '使用 Special Judge',\n  Special_Judge_Code: 'Special Judge 代码',\n  SPJ_language: 'SPJ 语言',\n  Compile: '编译',\n  TestCase: '测试用例',\n  IOMode: 'IO 类型',\n  InputFileName: '输入文件名',\n  OutputFileName: '输出文件名',\n  Type: '测试类型',\n  Input: '输入',\n  Output: '输出',\n  Score: '分数',\n  Hint: '提示',\n  Source: '来源',\n  Edit_Problem: '编辑问题',\n  Add_Problem: '添加问题',\n  High: '高',\n  Mid: '中',\n  Low: '低',\n  Tag: '标签',\n  New_Tag: '新增标签',\n  // ProblemList.vue\n  Contest_Problem_List: '比赛问题列表',\n  // Contest.vue\n  ContestTitle: '标题',\n  ContestDescription: '描述',\n  Contest_Start_Time: '开始时间',\n  Contest_End_Time: '结束时间',\n  Contest_Password: '密码',\n  Contest_Rule_Type: '规则',\n  Real_Time_Rank: '实时排名',\n  Contest_Status: '状态',\n  Allowed_IP_Ranges: '允许的 IP 范围',\n  CIDR_Network: 'CIDR 网络',\n  // Dashboard.vue\n  Last_Login: '最后登录状态',\n  System_Overview: '系统状况',\n  DashBoardJudge_Server: '判题服务器',\n  HTTPS_Status: 'HTTPS 状态',\n  Force_HTTPS: '强制使用 HTTPS',\n  CDN_HOST: 'CDN 主机',\n  // Login.vue\n  Welcome_to_Login: '欢迎登录 OnlineJudge 后台管理系统',\n  GO: '登录',\n  username: '用户名',\n  password: '密码'\n}\n"
  },
  {
    "path": "src/i18n/admin/zh-TW.js",
    "content": "export const m = {\n  // SideMenu.vue\n  Dashboard: '儀表板',\n  General: '基本設定',\n  User: '使用者管理',\n  Announcement: '公告管理',\n  System_Config: '系統設定',\n  Judge_Server: 'Judge 伺服器',\n  Prune_Test_Case: '測資',\n  Problem: '試題',\n  FromFile: '讀取檔案',\n  ToFile: '寫入檔案',\n  ShareSubmission: '分享提交',\n  Problem_List: '試題列表',\n  Create_Problem: '增加題目',\n  Export_Import_Problem: '匯入匯出題目',\n  Contest: '比賽',\n  Contest_List: '比賽列表',\n  Create_Contest: '建立比賽',\n  // User.vue\n  User_User: '使用者',\n  Import_User: '匯入使用者',\n  Generate_User: '生成使用者',\n  // User.vue-dialog\n  User_Info: '使用者資訊',\n  User_Username: '使用者名稱',\n  User_Real_Name: '真實姓名',\n  User_Email: '使用者 E-mail',\n  User_New_Password: '使用者密碼',\n  User_Type: '帳號類型',\n  Problem_Permission: '試題權限',\n  Two_Factor_Auth: '兩步驟驗證',\n  Is_Disabled: '是否禁用',\n  // Announcement.vue\n  General_Announcement: '公告',\n  Announcement_Title: '標題',\n  Announcement_Content: '內容',\n  Announcement_visible: '是否可見',\n  // Conf.vue\n  SMTP_Config: 'SMTP 設定',\n  Server: '伺服器',\n  Port: '連接埠',\n  Email: 'E-mail',\n  Password: '密碼',\n  Website_Config: '網站設定',\n  Base_Url: 'Base Url',\n  Name: '名稱',\n  Shortcut: '簡稱',\n  Footer: '頁尾',\n  Allow_Register: '是否允許註冊',\n  Submission_List_Show_All: '顯示全部題目的提交',\n  // JudgeServer.vue\n  Judge_Server_Token: 'Judge 伺服器 Token',\n  Judge_Server_Info: 'Judge 伺服器',\n  IP: 'IP',\n  Judger_Version: 'Judge 版本',\n  Service_URL: '伺服器 URL',\n  Last_Heartbeat: '上一次活動訊號',\n  Create_Time: '建立時間',\n  // PruneTestCase\n  Test_Case_Prune_Test_Case: '精簡測資',\n  // Problem.vue\n  Display_ID: '顯示 ID',\n  Title: '題目',\n  Description: '描述',\n  Input_Description: '輸入描述',\n  Output_Description: '輸出描述',\n  Time_Limit: '時間限制',\n  Memory_limit: '記憶體限制',\n  Difficulty: '難度',\n  Visible: '是否可見',\n  Languages: '可選程式語言',\n  Input_Samples: '輸入範例',\n  Output_Samples: '輸出範例',\n  Add_Sample: '加入範例',\n  Code_Template: '程式碼模板',\n  Special_Judge: 'Special Judge',\n  Use_Special_Judge: '使用 Special Judge',\n  Special_Judge_Code: 'Special Judge Code',\n  SPJ_language: 'SPJ language',\n  Compile: '編譯',\n  TestCase: '測資',\n  IOMode: 'IO 類型',\n  InputFileName: '輸入檔名',\n  OutputFileName: '輸出檔名',\n  Type: '測試類型',\n  Input: '輸入',\n  Output: '輸出',\n  Score: '分數',\n  Hint: '提示',\n  Source: '來源',\n  // Contest.vue\n  ContestTitle: '標題',\n  ContestDescription: '描述',\n  Contest_Start_Time: '開始時間',\n  Contest_End_Time: '結束時間',\n  Contest_Password: '密碼',\n  Contest_Rule_Type: '規則',\n  Real_Time_Rank: '即時排名',\n  Contest_Status: '狀態',\n  Allowed_IP_Ranges: '允許的 IP 範圍',\n  CIDR_Network: 'CIDR Network',\n  // Dashboard.vue\n  Last_Login: '最後登入狀態',\n  System_Overview: '系統狀況',\n  DashBoardJudge_Server: 'Judge 伺服器',\n  HTTPS_Status: 'HTTPS 狀態',\n  Force_HTTPS: '強制 HTTPS',\n  CDN_HOST: 'CDN HOST'\n}\n"
  },
  {
    "path": "src/i18n/index.js",
    "content": "import Vue from 'vue'\nimport VueI18n from 'vue-i18n'\n// ivew UI\nimport ivenUS from 'iview/dist/locale/en-US'\nimport ivzhCN from 'iview/dist/locale/zh-CN'\nimport ivzhTW from 'iview/dist/locale/zh-TW'\n// element UI\nimport elenUS from 'element-ui/lib/locale/lang/en'\nimport elzhCN from 'element-ui/lib/locale/lang/zh-CN'\nimport elzhTW from 'element-ui/lib/locale/lang/zh-TW'\n\nVue.use(VueI18n)\n\nconst languages = [\n  {value: 'en-US', label: 'English', iv: ivenUS, el: elenUS},\n  {value: 'zh-CN', label: '简体中文', iv: ivzhCN, el: elzhCN},\n  {value: 'zh-TW', label: '繁體中文', iv: ivzhTW, el: elzhTW}\n]\nconst messages = {}\n\n// combine admin and oj\nfor (let lang of languages) {\n  let locale = lang.value\n  let m = require(`./oj/${locale}`).m\n  Object.assign(m, require(`./admin/${locale}`).m)\n  let ui = Object.assign(lang.iv, lang.el)\n  messages[locale] = Object.assign({m: m}, ui)\n}\n// load language packages\nexport default new VueI18n({\n  locale: 'en-US',\n  messages: messages\n})\n\nexport {languages}\n"
  },
  {
    "path": "src/i18n/oj/en-US.js",
    "content": "export const m = {\n  // 404.vue\n  Go_Home: 'Go Home',\n  // Problem.vue\n  Description: 'Description',\n  Input: 'Input',\n  Output: 'Output',\n  Sample_Input: 'Sample Input',\n  Sample_Output: 'Sample Output',\n  Hint: 'Hint',\n  Source: 'Source',\n  Status: 'Status',\n  Information: 'Information',\n  Time_Limit: 'Time Limit',\n  Memory_Limit: 'Memory Limit',\n  Created: 'Created By',\n  Level: 'Level',\n  Score: 'Score',\n  Tags: 'Tags',\n  Show: 'Show',\n  Submit: 'Submit',\n  Submitting: 'Submitting',\n  Judging: 'Judging',\n  Wrong_Answer: 'Wrong Answer',\n  Statistic: 'Statistic',\n  Close: 'Close',\n  View_Contest: 'View Contest',\n  Are_you_sure_you_want_to_reset_your_code: 'Are you sure you want to reset your code?',\n  Code_can_not_be_empty: 'Code can not be empty',\n  Submit_code_successfully: 'Submit code successfully',\n  You_have_solved_the_problem: 'You have solved the problem',\n  Submitted_successfully: 'Submitted successfully',\n  You_have_submitted_a_solution: 'You have submitted a solution.',\n  Contest_has_ended: 'Contest has ended',\n  You_have_submission_in_this_problem_sure_to_cover_it: 'You have submission in this problem, sure to cover it?',\n  // About.vue\n  Compiler: 'Compiler',\n  Result_Explanation: 'Result Explanation',\n  Pending_Judging_Description: 'You solution will be judged soon, please wait for result.',\n  Compile_Error_Description: \"Failed to compile your source code. Click on the link to see compiler's output.\",\n  Accepted_Description: 'Congratulations. Your solution is correct.',\n  Wrong_Answer_Description: \"Your program's output doesn't match judger's answer.\",\n  Runtime_Error_Description: 'Your program terminated abnormally. Possible reasons are: segment fault, divided by zero or exited with code other than 0.',\n  Time_Limit_Exceeded_Description: 'The CPU time your program used has exceeded limit.',\n  Memory_Limit_Exceeded_Description: 'The memory your program actually used has exceeded limit.',\n  System_Error_Description: 'Oops, something has gone wrong with the judger. Please report this to administrator.',\n  // ACMContestRank.vue\n  Menu: 'Menu',\n  Chart: 'Chart',\n  Auto_Refresh: 'Auto Refresh',\n  RealName: 'RealName',\n  Force_Update: 'Force Update',\n  download_csv: 'download csv',\n  TotalTime: 'TotalTime',\n  Top_10_Teams: 'Top 10 Teams',\n  save_as_image: 'save as image',\n  // ACMHelper.vue\n  ACM_Helper: 'ACM Helper',\n  AC_Time: 'AC Time',\n  ProblemID: 'ProblemID',\n  First_Blood: 'First Blood',\n  Username: 'Username',\n  Checked: 'Checked',\n  Not_Checked: 'Not Checked',\n  Check_It: 'Check It',\n  // ACMRank.vue\n  ACM_Ranklist: 'ACM Ranklist',\n  mood: 'mood',\n  AC: 'AC',\n  Rating: 'Rating',\n  // Announcements.vue\n  Contest_Announcements: 'Contest Announcements',\n  By: 'By',\n  // ApplyResetPassword.vue\n  The_email_doesnt_exist: 'The email doesn\\'t exist',\n  Success: 'Success',\n  Password_reset_mail_sent: 'Password reset mail has been sent to your email，',\n  // FAQ.vue\n  Frequently_Asked_Questions: 'Frequently Asked Questions',\n  Where_is_the_input_and_the_output: 'Where is the input and the output?',\n  Where_is_the_input_and_the_output_answer_part_1: 'Your program shall read input from',\n  Standard_Input: 'Standard Input',\n  Where_is_the_input_and_the_output_answer_part_3: 'and write output to',\n  Standard_Output: 'Standard Output',\n  Where_is_the_input_and_the_output_answer_part_5: 'For example,you can use',\n  Where_is_the_input_and_the_output_answer_part_6: 'in C or',\n  Where_is_the_input_and_the_output_answer_part_7: 'in C++ to read from stdin,and use',\n  Where_is_the_input_and_the_output_answer_part_8: 'in C or',\n  Where_is_the_input_and_the_output_answer_part_9: 'in C++ to write to stdout.  User programs are not allowed to read or write files, or you will get a',\n  What_is_the_meaning_of_submission_execution_time: 'What\\'s the meaning of the submission execution time?',\n  What_is_the_meaning_of_submission_execution_time_answer: 'The onlinejudge might test your code multiple times with different input files. If your code gives the correct answer within the time limit for each input file, the execution time displayed is the max of the time spent for each test case. Otherwise, the execution time will have no sense.',\n  How_Can_I_use_CPP_Int64: 'How can I use C++ Int64?',\n  How_Can_I_use_CPP_Int64_answer_part_1: 'You should declare as',\n  How_Can_I_use_CPP_Int64_answer_part_2: 'and use with',\n  or: 'or',\n  using: 'using',\n  How_Can_I_use_CPP_Int64_answer_part_3: 'will result in',\n  Java_specifications: 'Java specifications?',\n  Java_specifications_answer_part_1: 'All programs must begin in a static main method in a',\n  Java_specifications_answer_part_2: 'class. Do not use public classes: even',\n  Java_specifications_answer_part_3: 'must be non public to avoid compile error.Use buffered I/O to avoid time limit exceeded due to excesive flushing.',\n  About_presentation_error: 'About presentation error?',\n  About_presentation_error_answer_part_1: 'There is no presentation error in this oj.The judger will trim the blacks and wraps in your ouput\\'s',\n  last: 'last',\n  About_presentation_error_answer_part_2: 'line.  if it\\'s still different with the correct output, the result will be',\n  How_to_report_bugs: 'How to report bugs about this oj?',\n  How_to_report_bugs_answer_part_1: 'The onlinejudge is open source, you can open an issue in',\n  How_to_report_bugs_answer_part_2: 'The details(like env, version..) about a bug is required, which will help us a lot to solve the bug. Certainly, we are very pleased to merge your pull requests.',\n  // Cancel.vue\n  Cancel: 'Cancel',\n  // ContestDetail.vue\n  Problems: 'Problems',\n  Announcements: 'Announcements',\n  Submissions: 'Submissions',\n  Rankings: 'Rankings',\n  Overview: 'Overview',\n  Admin_Helper: 'Admin Helper',\n  StartAt: 'StartAt',\n  EndAt: 'EndAt',\n  ContestType: 'ContestType',\n  Creator: 'Creator',\n  Public: 'Public',\n  Password_Protected: 'Password Protected',\n  // ContestList.vue\n  Rule: 'Rule',\n  OI: 'OI',\n  ACM: 'ACM',\n  Underway: 'Underway',\n  Not_Started: 'Not_Started',\n  Ended: 'Ended',\n  No_contest: 'No contest',\n  Please_login_first: 'Please login first!',\n  // ContestProblemList\n  Problems_List: 'Problems List',\n  No_Problems: 'No Problems',\n  // CodeMirror.vue\n  Language: 'Language',\n  Theme: 'Theme',\n  Reset_to_default_code_definition: 'Reset to default code definition',\n  Upload_file: 'Upload file',\n  Monokai: 'Monokai',\n  Solarized_Light: 'Solarized Light',\n  Material: 'Material',\n  // KatexEditor.vue\n  Latex_Editor: 'Latex Editor',\n  // NavBar.vue\n  Home: 'Home',\n  NavProblems: 'Problems',\n  Contests: 'Contests',\n  NavStatus: 'Status',\n  Rank: 'Rank',\n  ACM_Rank: 'ACM Rank',\n  OI_Rank: 'OI Rank',\n  About: 'About',\n  Judger: 'Judger',\n  FAQ: 'FAQ',\n  Login: 'Login',\n  Register: 'Register',\n  MyHome: 'Home',\n  MySubmissions: 'Submissions',\n  Settings: 'Settings',\n  Management: 'Management',\n  Logout: 'Logout',\n  Welcome_to: 'Welcome to',\n  // announcements.vue\n  Refresh: 'Refresh',\n  Back: 'Back',\n  No_Announcements: 'No Announcements',\n  // Setting.vue\n  Profile: 'Profile',\n  Account: 'Account',\n  Security: 'Security',\n  // AccoutSetting.vue\n  ChangePassword: 'Change Password',\n  ChangeEmail: 'Change Email',\n  Update_Password: 'Update Password',\n  // ProfileSetting.vue\n  Avatar_Setting: 'Avatar Setting',\n  Profile_Setting: 'Profile Setting',\n  // SecuritySettig\n  Sessions: 'Sessions',\n  Two_Factor_Authentication: 'Two Factor Authentication',\n  // Login.vue\n  LoginUsername: 'Username',\n  LoginPassword: 'Password',\n  TFA_Code: 'Code from your TFA app',\n  No_Account: 'No account? Register now!',\n  Forget_Password: 'Forget Password',\n  UserLogin: 'Login',\n  Welcome_back: 'Welcome back to OJ',\n  // OIRank.vue\n  OI_Ranklist: 'OI Ranklist',\n  // OIContestRank.vue\n  Total_Score: 'Total Score',\n  // ProblemList.vue\n  Problem_List: 'Problem List',\n  High: 'High',\n  Mid: 'Mid',\n  Low: 'Low',\n  All: 'All',\n  Reset: 'Reset',\n  Pick_One: 'Pick one',\n  Difficulty: 'Difficulty',\n  Total: 'Total',\n  AC_Rate: 'AC Rate',\n  // Register.vue\n  RegisterUsername: 'Username',\n  Email_Address: 'Email Address',\n  RegisterPassword: 'Password',\n  Password_Again: 'Password Again',\n  Captcha: 'Captcha',\n  UserRegister: 'Register',\n  Already_Registed: 'Already registed? Login now!',\n  The_username_already_exists: 'The username already exists.',\n  The_email_already_exists: 'The email already exists',\n  password_does_not_match: 'password does not match',\n  Thanks_for_registering: 'Thanks for your registering, you can login now',\n  // ResetPassword.vue and ApplyResetPassword.vue\n  Reset_Password: 'Lost Password',\n  RPassword: 'Password',\n  RPassword_Again: 'Password Again',\n  RCaptcha: 'Captcha',\n  ApplyEmail: 'Your Email Address',\n  Send_Password_Reset_Email: 'Send Password Reset Email',\n  Your_password_has_been_reset: 'Your password has been reset.',\n  // Save.vue\n  Save: 'Save',\n  // Simditor.vue\n  Uploading_is_in_progress: 'Uploading is in progress, are you sure to leave this page?',\n  // SubmissionDetails.vue\n  Lang: 'Lang',\n  Share: 'Share',\n  UnShare: 'UnShare',\n  Succeeded: 'Succeeded',\n  Real_Time: 'Real Time',\n  Signal: 'Signal',\n  // SubmissionList.vue\n  When: 'When',\n  ID: 'ID',\n  Time: 'Time',\n  Memory: 'Memory',\n  Author: 'Author',\n  Option: 'Option',\n  Mine: 'Mine',\n  Search_Author: 'Search Author',\n  Accepted: 'Accepted',\n  Time_Limit_Exceeded: 'Time Limit Exceeded',\n  Memory_Limit_Exceeded: 'Memory Limit Exceeded',\n  Runtime_Error: 'Runtime Error',\n  System_Error: 'System Error',\n  Pending: 'Pending',\n  Partial_Accepted: 'Partial Accepted',\n  Compile_Error: 'Compile Error',\n  Rejudge: 'Rejudge',\n  // UserHome.vue\n  UserHomeSolved: 'Solved',\n  UserHomeserSubmissions: 'Submissions',\n  UserHomeScore: 'Score',\n  List_Solved_Problems: 'List of solved problems',\n  UserHomeIntro: 'The guy is so lazy that has not solved any problem yet.'\n}\n"
  },
  {
    "path": "src/i18n/oj/zh-CN.js",
    "content": "export const m = {\n    // 404.vue\n  Go_Home: '返回主页',\n  // Problem.vue\n  Description: '题目描述',\n  Input: '输入',\n  Output: '输出',\n  Sample_Input: '输入样例',\n  Sample_Output: '输出样例',\n  Hint: '提示',\n  Source: '题目来源',\n  Status: '状态',\n  Information: '题目信息',\n  Time_Limit: '时间限制',\n  Memory_Limit: '内存限制',\n  Created: '出题人',\n  Level: '难度',\n  Score: '分数',\n  Tags: '标签',\n  Show: '显示',\n  Submit: '提交',\n  Submitting: '正在提交',\n  Judging: '正在评分',\n  Wrong_Answer: '答案错误',\n  Statistic: '统计',\n  Close: '关闭',\n  View_Contest: '查看比赛',\n  Are_you_sure_you_want_to_reset_your_code: '确定要重置代码吗？',\n  Code_can_not_be_empty: '不能提交空代码',\n  Submit_code_successfully: '成功提交代码',\n  You_have_solved_the_problem: '你已经解决了该问题',\n  Submitted_successfully: '成功提交',\n  You_have_submitted_a_solution: '你已经提交了解答',\n  Contest_has_ended: '比赛已结束',\n  You_have_submission_in_this_problem_sure_to_cover_it: '你已经提交了解答，确定要覆盖吗？',\n  // About.vue\n  Compiler: '编译器',\n  Result_Explanation: '结果解释',\n  Pending_Judging_Description: '您的解答将很快被测评，请等待结果。',\n  Compile_Error_Description: '无法编译您的源代码，点击链接查看编译器的输出。',\n  Accepted_Description: '你的解题方法是正确的。',\n  Wrong_Answer_Description: '你的程序输出结果与判题程序的答案不符。',\n  Runtime_Error_Description: '您的程序异常终止，可能的原因是：段错误，被零除或用非0的代码退出程序。',\n  Time_Limit_Exceeded_Description: '您的程序使用的 CPU 时间已超出限制。',\n  Memory_Limit_Exceeded_Description: '程序实际使用的内存已超出限制。',\n  System_Error_Description: '糟糕，判题程序出了问题。请报告给管理员。',\n  // ACMContestRank.vue\n  Menu: '菜单',\n  Chart: '图表',\n  Auto_Refresh: '自动刷新',\n  RealName: '真名',\n  Force_Update: '强制刷新',\n  download_csv: '下载 csv',\n  TotalTime: '总时间',\n  Top_10_Teams: '前 10 强队伍',\n  save_as_image: '保存图片',\n  // ACMHelper.vue\n  ACM_Helper: 'ACM 助手',\n  AC_Time: 'AC 时间',\n  ProblemID: '问题 ID',\n  First_Blood: '一血',\n  Username: '用户名',\n  Checked: '已检查',\n  Not_Checked: '未检查',\n  Check_It: '现在检查',\n  // ACMRank.vue\n  ACM_Ranklist: 'ACM 排名',\n  mood: '格言',\n  AC: 'AC',\n  Rating: '评分',\n  // Announcements.vue\n  Contest_Announcements: '比赛公告',\n  By: '创建人',\n  // ApplyResetPassword.vue\n  The_email_doesnt_exist: '该电子邮件地址不存在',\n  Success: '成功',\n  Password_reset_mail_sent: '密码重置邮件已发送。',\n  // FAQ.vue\n  Frequently_Asked_Questions: '常见问题',\n  Where_is_the_input_and_the_output: '输入和输出在哪里？',\n  Where_is_the_input_and_the_output_answer_part_1: '您的程序应从',\n  Standard_Input: '标准输入',\n  Where_is_the_input_and_the_output_answer_part_3: '读取输入，并将输出写入',\n  Standard_Output: '标准输出',\n  Where_is_the_input_and_the_output_answer_part_5: '例如，您可以在 C 中使用',\n  Where_is_the_input_and_the_output_answer_part_6: '或在 C ++ 中使用',\n  Where_is_the_input_and_the_output_answer_part_7: '读取，并在 C 中使用',\n  Where_is_the_input_and_the_output_answer_part_8: '或在 C ++ 中使用',\n  Where_is_the_input_and_the_output_answer_part_9: '写入 stdout。用户程序不允许读取或写入文件，否则您将收到',\n  What_is_the_meaning_of_submission_execution_time: '提交执行时间是什么意思？',\n  What_is_the_meaning_of_submission_execution_time_answer: 'OnlineJudge 可能会使用不同的输入文件多次测试您的代码。如果您的代码在每个输入文件的时间限制内给出正确的答案，则显示的执行时间是每个测试用例所花费的最大时间。否则，执行时间将毫无意义。',\n  How_Can_I_use_CPP_Int64: '如何使用 C ++ Int64？',\n  How_Can_I_use_CPP_Int64_answer_part_1: '您应该声明',\n  How_Can_I_use_CPP_Int64_answer_part_2: '并与',\n  or: '或',\n  using: '一起使用，使用',\n  How_Can_I_use_CPP_Int64_answer_part_3: ' 将导致',\n  Java_specifications: 'Java 规范？',\n  Java_specifications_answer_part_1: '所有程序都必须以',\n  Java_specifications_answer_part_2: '类的静态 main 方法开始。不要使用公共类：即使',\n  Java_specifications_answer_part_3: '也必须是非公共类以避免编译错误使用缓冲I / O以避免由于过度刷新而导致超出时间限制',\n  About_presentation_error: '关于输出格式错误？',\n  About_presentation_error_answer_part_1: '该 OJ 中没有输出格式错误。Judger将对自动对输出修整然后包裹在输出的',\n  last: '最后',\n  About_presentation_error_answer_part_2: '一行中。如果仍然与正确的输出不同，则结果将是',\n  How_to_report_bugs: '如何报告有关此 OJ 的错误？',\n  How_to_report_bugs_answer_part_1: 'onlinejudge 是开源的，您可以到',\n  How_to_report_bugs_answer_part_2: '提交问题。需要有关错误的详细信息（例如env，版本..），这将帮助我们极大地解决该错误。当然，我们很高兴合并您的请求。',\n  // Cancel.vue\n  Cancel: '取消',\n  // ContestDetail.vue\n  Problems: '题目',\n  Announcements: '公告',\n  Submissions: '提交信息',\n  Rankings: '排名',\n  Overview: '概要',\n  Admin_Helper: '管理员助手',\n  StartAt: '开始时间',\n  EndAt: '结束时间',\n  ContestType: '比赛类型',\n  Creator: '发起人',\n  Public: '公开',\n  Password_Protected: '密码保护',\n  // ContestList.vue\n  Rule: '赛制',\n  OI: 'OI',\n  ACM: 'ACM',\n  Underway: '进行中',\n  Not_Started: '筹备中',\n  Ended: '已结束',\n  No_contest: '尚无练习或比赛',\n  Please_login_first: '请先登录！',\n  // ContestProblemList.vue\n  Problems_List: '问题列表',\n  No_Problems: '尚无问题',\n  // CodeMirror.vue\n  Language: '语言',\n  Theme: '主题',\n  Reset_to_default_code_definition: '重设返回默认代码设置',\n  Upload_file: '上传文件',\n  Monokai: '物界',\n  Solarized_Light: '日光灯',\n  Material: '材料',\n  // KatexEditor.vue\n  Latex_Editor: 'Latex 编辑器',\n  // NavBar.vue\n  Home: '首页',\n  NavProblems: '问题',\n  Contests: '练习&比赛',\n  NavStatus: '状态',\n  Rank: '排名',\n  ACM_Rank: 'ACM 排名',\n  OI_Rank: 'OI 排名',\n  About: '关于',\n  Judger: '评分器',\n  FAQ: '常见问题',\n  Login: '登录',\n  Register: '注册',\n  MyHome: '我的主页',\n  MySubmissions: '我的提交',\n  Settings: '我的设置',\n  Management: '后台管理',\n  Logout: '退出',\n  Welcome_to: '欢迎来到',\n  // announcements.vue\n  Refresh: '刷新',\n  Back: '返回',\n  No_Announcements: '暂无公告',\n  // Setting.vue\n  Profile: '个人信息设置',\n  Account: '账号设置',\n  Security: '安全设置',\n  // AccoutSetting.vue\n  ChangePassword: '更改密码',\n  ChangeEmail: '更改邮箱',\n  Update_Password: '更新密码',\n  // ProfileSetting.vue\n  Avatar_Setting: '头像设置',\n  Profile_Setting: '个人信息设置',\n  // SecuritySettig\n  Sessions: '登录记录',\n  Two_Factor_Authentication: '双因素认证',\n  // Login.vue\n  LoginUsername: '用户名',\n  LoginPassword: '密码',\n  TFA_Code: 'TFA App 双因素认证码',\n  No_Account: '还没账号，立即注册!',\n  Forget_Password: '忘记密码',\n  UserLogin: '登录',\n  Welcome_back: '欢迎回来',\n  // OIRank.vue\n  OI_Ranklist: 'OI 排名',\n  // OIContestRank.vue\n  Total_Score: '总分',\n  // ProblemList.vue\n  Problem_List: '问题列表',\n  High: '高',\n  Mid: '中',\n  Low: '低',\n  All: '全部',\n  Reset: '重置',\n  Pick_One: '选择',\n  Difficulty: '难度',\n  Total: '总数',\n  AC_Rate: '通过率',\n  // Register.vue\n  RegisterUsername: '用户名',\n  Email_Address: '电子邮箱',\n  RegisterPassword: '密码',\n  Password_Again: '确认密码',\n  Captcha: '验证码',\n  UserRegister: '注册',\n  Already_Registed: '已经注册？现在登录!',\n  The_username_already_exists: '该用户名已存在',\n  The_email_already_exists: '该电子邮件地址已存在',\n  password_does_not_match: '密码不匹配',\n  Thanks_for_registering: '感谢注册，您现在可以登录了',\n  // ResetPassword.vue and ApplyResetPassword.vue\n  Reset_Password: '重置密码',\n  RPassword: '密码',\n  RPassword_Again: '确认密码',\n  RCaptcha: '验证码',\n  ApplyEmail: '电子邮箱',\n  Send_Password_Reset_Email: '发送重置密码到邮箱',\n  Your_password_has_been_reset: '您的密码已重置',\n  // Save.vue\n  Save: '保存',\n  // Simditor.vue\n  Uploading_is_in_progress: '正在上传，您确定要离开当前页面吗？',\n  // SubmissionDetails.vue\n  Lang: '语言',\n  Share: '分享',\n  UnShare: '不分享',\n  Succeeded: '成功',\n  Real_Time: '真实时间',\n  Signal: '信号',\n  // SubmissionList.vue\n  When: '时间',\n  ID: 'ID',\n  Time: '时间',\n  Memory: '内存',\n  Author: '作者',\n  Option: '选项',\n  Mine: '我的',\n  Search_Author: '搜索作者',\n  Accepted: '答案正确',\n  Time_Limit_Exceeded: '运行超时',\n  Memory_Limit_Exceeded: '内存超限',\n  Runtime_Error: '运行时错误',\n  System_Error: '系统错误',\n  Pending: '等待评分',\n  Partial_Accepted: '部分正确',\n  Compile_Error: '编译失败',\n  Rejudge: '重新评分',\n  // UserHome.vue\n  UserHomeSolved: '已解决问题的数量',\n  UserHomeserSubmissions: '提交次数',\n  UserHomeScore: '分数',\n  List_Solved_Problems: '已解决问题的列表',\n  UserHomeIntro: '这个家伙太懒了，还没有做题呢...'\n}\n"
  },
  {
    "path": "src/i18n/oj/zh-TW.js",
    "content": "export const m = {\n  // 404.vue\n  Go_Home: '回到首頁',\n  // Problem.vue\n  Description: '題目描述',\n  Input: '輸入',\n  Output: '輸出',\n  Sample_Input: '輸入範例',\n  Sample_Output: '輸出範例',\n  Hint: '提示',\n  Source: '題目來源',\n  Status: '狀態',\n  Information: '題目資訊',\n  Time_Limit: '時間限制',\n  Memory_Limit: '記憶體限制',\n  Created: '出題者',\n  Level: '難度',\n  Score: '分數',\n  Tags: '標籤',\n  Show: '顯示',\n  Submit: '提交',\n  Submitting: '提交中',\n  Judging: '評分中',\n  Wrong_Answer: '答案錯誤',\n  Statistic: '統計',\n  Close: '關閉',\n  View_Contest: '查看比賽',\n  Are_you_sure_you_want_to_reset_your_code: '你確定要重置你的程式碼嗎?',\n  Code_can_not_be_empty: '你不能提交空的程式碼',\n  Submit_code_successfully: '成功提交程式碼',\n  You_have_solved_the_problem: '你已經解決了該試題',\n  Submitted_successfully: '成功提交',\n  You_have_submitted_a_solution: '你已經提交了解答.',\n  Contest_has_ended: '比賽已經結束',\n  You_have_submission_in_this_problem_sure_to_cover_it: '你已經提交了解答，確定要覆蓋嗎?',\n  // About.vue\n  Compiler: '編譯器',\n  Result_Explanation: '結果說明',\n  Pending_Judging_Description: '您的答案即將進行評分，請等待結果。',\n  Compile_Error_Description: '無法編譯您的原始碼，請點選連結以檢視編譯器的輸出。',\n  Accepted_Description: '您的解題方法是正確的。',\n  Wrong_Answer_Description: '您程式的輸出結果與標準程式的答案不符。',\n  Runtime_Error_Description: '您的程式異常終止，可能的原因是: 記憶體區段錯誤、被零除或結束程式時傳回非 0 的值。',\n  Time_Limit_Exceeded_Description: '您的程式使用的 CPU 時間已超出限制。',\n  Memory_Limit_Exceeded_Description: '程式實際使用的記憶體已超出限制。',\n  System_Error_Description: 'Judge 系統發生錯誤。請回報系統管理員。',\n  // ACMContestRank.vue\n  Menu: '選單',\n  Chart: '圖表',\n  Auto_Refresh: '自動重新載入',\n  RealName: '真實名稱',\n  Force_Update: '強制重新載入',\n  download_csv: '下載csv檔',\n  TotalTime: '總時間',\n  Top_10_Teams: '前10名隊伍',\n  save_as_image: '保存圖片',\n  // ACMHelper.vue\n  ACM_Helper: 'ACM助手',\n  AC_Time: 'AC 時間',\n  ProblemID: '題目ID',\n  First_Blood: '頭香',\n  Username: '使用者名稱',\n  Checked: '已檢查',\n  Not_Checked: '還未檢查',\n  Check_It: '現在檢查',\n  // ACMRank.vue\n  ACM_Ranklist: 'ACM 排名',\n  mood: '個人狀態',\n  AC: 'AC',\n  Rating: '評分',\n  // Announcements.vue\n  Contest_Announcements: '比賽公告',\n  By: '創建者',\n  // ApplyResetPassword.vue\n  The_email_doesnt_exist: '此電子郵件並不存在',\n  Success: '成功',\n  Password_reset_mail_sent: '已發送重置密碼之電子郵件',\n  // FAQ.vue\n  Frequently_Asked_Questions: '常見問題',\n  Where_is_the_input_and_the_output: '輸入與輸出在哪邊?',\n  Where_is_the_input_and_the_output_answer_part_1: '你的程式會從',\n  Standard_Input: '標準輸入',\n  Where_is_the_input_and_the_output_answer_part_3: '讀取輸入，並且將結果輸出到',\n  Standard_Output: '標準輸出',\n  Where_is_the_input_and_the_output_answer_part_5: '例如，你可以在 C 中使用',\n  Where_is_the_input_and_the_output_answer_part_6: '或在 C++ 中使用',\n  Where_is_the_input_and_the_output_answer_part_7: '讀取輸入，並在 C 中使用',\n  Where_is_the_input_and_the_output_answer_part_8: '或在 C++ 中使用',\n  Where_is_the_input_and_the_output_answer_part_9: '來輸出到stdout。你的程式不被允許讀取或著是寫入檔案，否則你將獲得',\n  What_is_the_meaning_of_submission_execution_time: '提交執行時間是什麼意思?',\n  What_is_the_meaning_of_submission_execution_time_answer: '線上解題系統可能會使用不同的輸入檔案來多次測試你的程式碼。如果你的程式碼在每個輸入檔案的時間限制內給出正確的答案，則顯示的執行時間是每個測資所花費的最大時間。否則，執行時間將毫無意義',\n  How_Can_I_use_CPP_Int64: '我要如何使用 C++ Int64?',\n  How_Can_I_use_CPP_Int64_answer_part_1: '你應該宣告為',\n  How_Can_I_use_CPP_Int64_answer_part_2: '並且與',\n  or: '或',\n  using: '一起使用，使用',\n  How_Can_I_use_CPP_Int64_answer_part_3: '將導致',\n  Java_specifications: 'Java 規範?',\n  Java_specifications_answer_part_1: '所有的程式都必須以',\n  Java_specifications_answer_part_2: '類別的靜態main方法開始執行。不要使用public類別：即使',\n  Java_specifications_answer_part_3: '也必須是非public類別以避免編譯錯誤使用緩衝I/O以避免過度重載而導致超出時間限制',\n  About_presentation_error: '關於輸出格式錯誤?',\n  About_presentation_error_answer_part_1: '此OJ中沒有輸出格式錯誤。Judge系統將自動整理輸出然後包裹在輸出的',\n  last: '最後',\n  About_presentation_error_answer_part_2: '一行中。如果仍然與正確的輸出不同，則結果將是',\n  How_to_report_bugs: '如何回報關於此OJ的錯誤?',\n  How_to_report_bugs_answer_part_1: '此線上解題系統是開源的，你可以到',\n  How_to_report_bugs_answer_part_2: '提交問題。請提供有關錯誤的詳細訊息(如env，版本...)，這將極大地幫助我們解決該錯誤。當然，我們十分樂意合併你的請求',\n  // Cancel.vue\n  Cancel: '取消',\n  // ContestDetail.vue\n  Problems: '題目',\n  Announcements: '公告',\n  Submissions: '提交資訊',\n  Rankings: '排名',\n  Overview: '概要',\n  Admin_Helper: '管理員助手',\n  StartAt: '開始時間',\n  EndAt: '結束時間',\n  ContestType: '比賽類型',\n  Creator: '發起人',\n  Public: '公開',\n  Password_Protected: '密碼保護',\n  // ContestList.vue\n  Rule: '賽制',\n  OI: 'OI',\n  ACM: 'ACM',\n  Underway: '進行中',\n  Not_Started: '準備中',\n  Ended: '已結束',\n  No_contest: '目前無任何比賽',\n  Please_login_first: '請先登入!',\n  // ContestProblemList.vue\n  Problems_List: '試題列表',\n  No_Problems: '暫無試題',\n  // CodeMirror.vue\n  Language: '語言',\n  Theme: '主題',\n  Reset_to_default_code_definition: '使用預設程式碼設定',\n  Upload_file: '上傳文件',\n  Monokai: 'Monokai',\n  Solarized_Light: '日光燈',\n  Material: '材料',\n  // KatexEditor.vue\n  Latex_Editor: 'Latex 編輯器',\n  // NavBar.vue\n  Home: '首頁',\n  NavProblems: '試題',\n  Contests: '比賽',\n  NavStatus: '狀態',\n  Rank: '排名',\n  ACM_Rank: 'ACM 排名',\n  OI_Rank: 'OI 排名',\n  About: '關於',\n  Judger: 'Judge 說明',\n  FAQ: '常見問題',\n  Login: '登入',\n  Register: '註冊',\n  MyHome: '我的首頁',\n  MySubmissions: '我的提交',\n  Settings: '我的設定',\n  Management: '後台管理',\n  Logout: '登出',\n  Welcome_to: '歡迎來到',\n  // announcements.vue\n  Refresh: '重新整理',\n  Back: '返回',\n  No_Announcements: '暫無公告',\n  // Setting.vue\n  Profile: '個人資訊設定',\n  Account: '帳號設定',\n  Security: '安全設定',\n  // AccoutSetting.vue\n  ChangePassword: '更改密碼',\n  ChangeEmail: '更改 E-mail',\n  Update_Password: '更新密碼',\n  // ProfileSetting.vue\n  Avatar_Setting: '大頭貼設定',\n  Profile_Setting: '個人資訊設定',\n  // SecuritySettig\n  Sessions: '登入記錄',\n  Two_Factor_Authentication: '兩步驟驗證',\n  // Login.vue\n  LoginUsername: '使用者名稱',\n  LoginPassword: '密碼',\n  TFA_Code: '兩步驟驗證碼',\n  No_Account: '沒有帳號，立即註冊!',\n  Forget_Password: '忘記密碼',\n  UserLogin: '登入',\n  Welcome_back: '歡迎回來',\n  // OIRank.vue\n  OI_Ranklist: 'OI 排名',\n  // OIContestRank.vue\n  Total_Score: '總分',\n  // ProblemList.vue\n  Problem_List: '試題列表',\n  High: '高級',\n  Mid: '中級',\n  Low: '初級',\n  All: '全部',\n  Reset: '重置',\n  Pick_One: '選擇',\n  Difficulty: '難度',\n  Total: '總數',\n  AC_Rate: '通過率',\n  // Register.vue\n  RegisterUsername: '使用者名稱',\n  Email_Address: 'E-mail',\n  RegisterPassword: '密碼',\n  Password_Again: '確認密碼',\n  Captcha: '驗證碼',\n  UserRegister: '註冊',\n  Already_Registed: '已經註冊? 現在登入!',\n  The_username_already_exists: '此使用者名稱已經存在.',\n  The_email_already_exists: '此電子郵件地址已被註冊',\n  password_does_not_match: '無密碼相符',\n  Thanks_for_registering: '感謝你的註冊，現在你可以登入了',\n  // ResetPassword.vue and ApplyResetPassword.vue\n  Reset_Password: '重設密碼',\n  RPassword: '密碼',\n  RPassword_Again: '確認密碼',\n  RCaptcha: '驗證碼',\n  ApplyEmail: 'E-mail',\n  Send_Password_Reset_Email: '傳送密碼重設 E-mail',\n  Your_password_has_been_reset: '你的密碼已重置',\n  // Save.vue\n  Save: '存檔',\n  // Simditor.vue\n  Uploading_is_in_progress: '上傳作業正在執行，你確定要離開當前頁面嗎??',\n  // SubmissionDetails.vue\n  Lang: '語言',\n  Share: '分享',\n  UnShare: '不分享',\n  Succeeded: '成功',\n  Real_Time: '真實時間',\n  Signal: '訊號',\n  // SubmissionList.vue\n  When: '時間',\n  ID: 'ID',\n  Time: '時間',\n  Memory: '記憶體空間',\n  Author: '作者',\n  Option: '選項',\n  Mine: '我的',\n  Search_Author: '搜尋作者',\n  Accepted: '答案正確',\n  Time_Limit_Exceeded: '超出時間限制',\n  Memory_Limit_Exceeded: '超出記憶體空間限制',\n  Runtime_Error: 'Runtime Error',\n  System_Error: 'System Error',\n  Pending: 'Pending',\n  Partial_Accepted: 'Partial Accepted',\n  Compile_Error: 'Compile Error',\n  Rejudge: 'Rejudge',\n  // UserHome.vue\n  UserHomeSolved: '已解題數量',\n  UserHomeserSubmissions: '提交次數',\n  UserHomeScore: '分數',\n  List_Solved_Problems: '已完成題目的列表',\n  UserHomeIntro: '這個使用者尚未解題...'\n}\n"
  },
  {
    "path": "src/pages/admin/App.vue",
    "content": "<template>\n  <div id=\"app\">\n    <router-view></router-view>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'app',\n    components: {}\n  }\n</script>\n\n<style lang=\"less\">\n  body {\n    margin: 0;\n    padding: 0;\n    font-family: \"Helvetica Neue\",Helvetica,\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",\"微软雅黑\",Arial,sans-serif;\n    font-size: 14px;\n    -webkit-font-smoothing: antialiased;\n    background-color: #324157;\n  }\n\n  #app {\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    width: 100%;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/api.js",
    "content": "import Vue from 'vue'\nimport router from './router'\nimport axios from 'axios'\nimport utils from '@/utils/utils'\n\nVue.prototype.$http = axios\naxios.defaults.baseURL = '/api'\naxios.defaults.xsrfHeaderName = 'X-CSRFToken'\naxios.defaults.xsrfCookieName = 'csrftoken'\n\nexport default {\n  // 登录\n  login (username, password) {\n    return ajax('login', 'post', {\n      data: {\n        username,\n        password\n      }\n    })\n  },\n  logout () {\n    return ajax('logout', 'get')\n  },\n  getProfile () {\n    return ajax('profile', 'get')\n  },\n  // 获取公告列表\n  getAnnouncementList (offset, limit) {\n    return ajax('admin/announcement', 'get', {\n      params: {\n        paging: true,\n        offset,\n        limit\n      }\n    })\n  },\n  // 删除公告\n  deleteAnnouncement (id) {\n    return ajax('admin/announcement', 'delete', {\n      params: {\n        id\n      }\n    })\n  },\n  // 修改公告\n  updateAnnouncement (data) {\n    return ajax('admin/announcement', 'put', {\n      data\n    })\n  },\n  // 添加公告\n  createAnnouncement (data) {\n    return ajax('admin/announcement', 'post', {\n      data\n    })\n  },\n  // 获取用户列表\n  getUserList (offset, limit, keyword) {\n    let params = {paging: true, offset, limit}\n    if (keyword) {\n      params.keyword = keyword\n    }\n    return ajax('admin/user', 'get', {\n      params: params\n    })\n  },\n  // 获取单个用户信息\n  getUser (id) {\n    return ajax('admin/user', 'get', {\n      params: {\n        id\n      }\n    })\n  },\n  // 编辑用户\n  editUser (data) {\n    return ajax('admin/user', 'put', {\n      data\n    })\n  },\n  deleteUsers (id) {\n    return ajax('admin/user', 'delete', {\n      params: {\n        id\n      }\n    })\n  },\n  importUsers (users) {\n    return ajax('admin/user', 'post', {\n      data: {\n        users\n      }\n    })\n  },\n  generateUser (data) {\n    return ajax('admin/generate_user', 'post', {\n      data\n    })\n  },\n  getLanguages () {\n    return ajax('languages', 'get')\n  },\n  getSMTPConfig () {\n    return ajax('admin/smtp', 'get')\n  },\n  createSMTPConfig (data) {\n    return ajax('admin/smtp', 'post', {\n      data\n    })\n  },\n  editSMTPConfig (data) {\n    return ajax('admin/smtp', 'put', {\n      data\n    })\n  },\n  testSMTPConfig (email) {\n    return ajax('admin/smtp_test', 'post', {\n      data: {\n        email\n      }\n    })\n  },\n  getWebsiteConfig () {\n    return ajax('admin/website', 'get')\n  },\n  editWebsiteConfig (data) {\n    return ajax('admin/website', 'post', {\n      data\n    })\n  },\n  getJudgeServer () {\n    return ajax('admin/judge_server', 'get')\n  },\n  deleteJudgeServer (hostname) {\n    return ajax('admin/judge_server', 'delete', {\n      params: {\n        hostname: hostname\n      }\n    })\n  },\n  updateJudgeServer (data) {\n    return ajax('admin/judge_server', 'put', {\n      data\n    })\n  },\n  getInvalidTestCaseList () {\n    return ajax('admin/prune_test_case', 'get')\n  },\n  pruneTestCase (id) {\n    return ajax('admin/prune_test_case', 'delete', {\n      params: {\n        id\n      }\n    })\n  },\n  createContest (data) {\n    return ajax('admin/contest', 'post', {\n      data\n    })\n  },\n  getContest (id) {\n    return ajax('admin/contest', 'get', {\n      params: {\n        id\n      }\n    })\n  },\n  editContest (data) {\n    return ajax('admin/contest', 'put', {\n      data\n    })\n  },\n  getContestList (offset, limit, keyword) {\n    let params = {paging: true, offset, limit}\n    if (keyword) {\n      params.keyword = keyword\n    }\n    return ajax('admin/contest', 'get', {\n      params: params\n    })\n  },\n  getContestAnnouncementList (contestID) {\n    return ajax('admin/contest/announcement', 'get', {\n      params: {\n        contest_id: contestID\n      }\n    })\n  },\n  createContestAnnouncement (data) {\n    return ajax('admin/contest/announcement', 'post', {\n      data\n    })\n  },\n  deleteContestAnnouncement (id) {\n    return ajax('admin/contest/announcement', 'delete', {\n      params: {\n        id\n      }\n    })\n  },\n  updateContestAnnouncement (data) {\n    return ajax('admin/contest/announcement', 'put', {\n      data\n    })\n  },\n  getProblemTagList (params) {\n    return ajax('problem/tags', 'get', {\n      params\n    })\n  },\n  compileSPJ (data) {\n    return ajax('admin/compile_spj', 'post', {\n      data\n    })\n  },\n  createProblem (data) {\n    return ajax('admin/problem', 'post', {\n      data\n    })\n  },\n  editProblem (data) {\n    return ajax('admin/problem', 'put', {\n      data\n    })\n  },\n  deleteProblem (id) {\n    return ajax('admin/problem', 'delete', {\n      params: {\n        id\n      }\n    })\n  },\n  getProblem (id) {\n    return ajax('admin/problem', 'get', {\n      params: {\n        id\n      }\n    })\n  },\n  getProblemList (params) {\n    params = utils.filterEmptyValue(params)\n    return ajax('admin/problem', 'get', {\n      params\n    })\n  },\n  getContestProblemList (params) {\n    params = utils.filterEmptyValue(params)\n    return ajax('admin/contest/problem', 'get', {\n      params\n    })\n  },\n  getContestProblem (id) {\n    return ajax('admin/contest/problem', 'get', {\n      params: {\n        id\n      }\n    })\n  },\n  createContestProblem (data) {\n    return ajax('admin/contest/problem', 'post', {\n      data\n    })\n  },\n  editContestProblem (data) {\n    return ajax('admin/contest/problem', 'put', {\n      data\n    })\n  },\n  deleteContestProblem (id) {\n    return ajax('admin/contest/problem', 'delete', {\n      params: {\n        id\n      }\n    })\n  },\n  makeContestProblemPublic (data) {\n    return ajax('admin/contest_problem/make_public', 'post', {\n      data\n    })\n  },\n  addProblemFromPublic (data) {\n    return ajax('admin/contest/add_problem_from_public', 'post', {\n      data\n    })\n  },\n  getReleaseNotes () {\n    return ajax('admin/versions', 'get')\n  },\n  getDashboardInfo () {\n    return ajax('admin/dashboard_info', 'get')\n  },\n  getSessions () {\n    return ajax('sessions', 'get')\n  },\n  exportProblems (data) {\n    return ajax('export_problem', 'post', {\n      data\n    })\n  }\n}\n\n/**\n * @param url\n * @param method get|post|put|delete...\n * @param params like queryString. if a url is index?a=1&b=2, params = {a: '1', b: '2'}\n * @param data post data, use for method put|post\n * @returns {Promise}\n */\nfunction ajax (url, method, options) {\n  if (options !== undefined) {\n    var {params = {}, data = {}} = options\n  } else {\n    params = data = {}\n  }\n  return new Promise((resolve, reject) => {\n    axios({\n      url,\n      method,\n      params,\n      data\n    }).then(res => {\n      // API正常返回(status=20x), 是否错误通过有无error判断\n      if (res.data.error !== null) {\n        Vue.prototype.$error(res.data.data)\n        reject(res)\n        // // 若后端返回为登录，则为session失效，应退出当前登录用户\n        if (res.data.data.startsWith('Please login')) {\n          router.push({name: 'login'})\n        }\n      } else {\n        resolve(res)\n        if (method !== 'get') {\n          Vue.prototype.$success('Succeeded')\n        }\n      }\n    }, res => {\n      // API请求异常，一般为Server error 或 network error\n      reject(res)\n      Vue.prototype.$error(res.data.data)\n    })\n  })\n}\n"
  },
  {
    "path": "src/pages/admin/components/Accordion.vue",
    "content": "<template>\n  <div class=\"accordion\">\n    <header>\n      <h2>{{title}}</h2>\n      <div class=\"header_right\">\n        <slot name=\"header\"></slot>\n      </div>\n    </header>\n    <div class=\"body\" v-show=\"isOpen\">\n      <slot></slot>\n    </div>\n    <footer @click=\"isOpen = !isOpen\"><i :class=\"{'rotate': !isOpen}\" class=\"el-icon-caret-top\"></i></footer>\n  </div>\n</template>\n\n<script>\n  export default{\n    name: 'Accordion',\n    props: {\n      title: {\n        type: String,\n        required: true\n      }\n    },\n    data () {\n      return {\n        isOpen: true\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n.accordion{\n  border: 1px solid #eaeefb;\n  header{\n    position: relative;\n    h2{\n      font-size: 14px;\n      margin: 0 0 0 10px;\n      line-height: 50px;\n    }\n    .header_right{\n      right: 5px;\n      top: 5px;\n      position: absolute;\n    }\n  }\n  .body{\n    background-color: #f9fafc;\n    border-top: 1px solid #eaeefb;\n    clear: both;\n    overflow: hidden;\n    padding: 15px 10px;\n  }\n  footer{\n    border-top: 1px solid #eaeefb;\n    height: 36px;\n    box-sizing: border-box;\n    background-color: #fff;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n    text-align: center;\n    margin-top: -1px;\n    color: #d3dce6;\n    cursor: pointer;\n    transition: .2s;\n    &:hover{\n      background-color: #f9fafc;\n    }\n    .rotate{\n      transform: rotate(180deg);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/CodeMirror.vue",
    "content": "<template>\n  <codemirror v-model=\"currentValue\" :options=\"options\" ref=\"editor\"></codemirror>\n</template>\n<script>\n  import { codemirror } from 'vue-codemirror-lite'\n  import 'codemirror/mode/clike/clike.js'\n  import 'codemirror/mode/python/python.js'\n  import 'codemirror/theme/solarized.css'\n\n  export default {\n    name: 'CodeMirror',\n    data () {\n      return {\n        currentValue: '',\n        options: {\n          mode: 'text/x-csrc',\n          lineNumbers: true,\n          lineWrapping: false,\n          theme: 'solarized',\n          tabSize: 4,\n          line: true,\n          foldGutter: true,\n          gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],\n          autofocus: true\n        }\n      }\n    },\n    components: {\n      codemirror\n    },\n    props: {\n      value: {\n        type: String,\n        default: ''\n      },\n      mode: {\n        type: String,\n        default: 'text/x-csrc'\n      }\n    },\n    mounted () {\n      this.currentValue = this.value\n      this.$refs.editor.editor.setOption('mode', this.mode)\n    },\n    watch: {\n      'value' (val) {\n        if (this.currentValue !== val) {\n          this.currentValue = val\n        }\n      },\n      'currentValue' (newVal, oldVal) {\n        if (newVal !== oldVal) {\n          this.$emit('change', newVal)\n          this.$emit('input', newVal)\n        }\n      },\n      'mode' (newVal) {\n        this.$refs.editor.editor.setOption('mode', newVal)\n      }\n    }\n  }\n</script>\n\n<style>\n  .CodeMirror {\n    height: auto !important;\n  }\n\n  .CodeMirror-scroll {\n    min-height: 300px;\n    max-height: 1000px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/KatexEditor.vue",
    "content": "<template>\n  <el-form>\n    <el-form-item :label=\"$t('m.Input')\">\n      <el-input type=\"textarea\" v-model=\"input\" @change=\"changeInput\" @keyup.enter.native=\"changeInput\"></el-input>\n    </el-form-item>\n\n    <el-form-item :label=\"$t('m.Output')\">\n    </el-form-item>\n    <div v-html=\"text\"></div>\n  </el-form>\n</template>\n\n<script>\n  import katex from 'katex'\n  export default {\n    name: '',\n    data () {\n      return {\n        input: 'c = \\\\pm\\\\sqrt{a^2 + b^2}',\n        text: ''\n      }\n    },\n    mounted () {\n      this.text = this.renderTex(this.input)\n    },\n    methods: {\n      renderTex (data) {\n        return katex.renderToString(data, {\n          displayMode: true,\n          throwOnError: false\n        })\n      },\n      changeInput () {\n        try {\n          this.text = this.renderTex(this.input)\n        } catch (e) {\n          this.text = '<p style=\"text-align: center\"><span style=\"color:red\">Error Input</span></p>'\n        }\n      }\n    }\n  }\n</script>\n\n<style scoped>\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/Panel.vue",
    "content": "<template>\n  <div class=\"panel\" :class=\"{'small': small}\">\n    <header>\n      <div class=\"title\">\n        <template v-if=\"$slots.title\">\n          <slot name=\"title\"></slot>\n        </template>\n        <template v-else>\n          {{title}}\n        </template>\n      </div>\n\n      <div class=\"header_right\">\n        <slot name=\"header\"></slot>\n      </div>\n    </header>\n\n    <div class=\"body\">\n      <slot></slot>\n    </div>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'Panel',\n    props: {\n      title: {\n        type: String,\n        required: false\n      },\n      small: {\n        type: Boolean,\n        default: false\n      }\n    }\n  }\n</script>\n<style scoped lang=\"less\">\n  .panel {\n    margin-bottom: 20px;\n    background-color: #fff;\n    border: 1px solid transparent;\n    border-radius: 4px;\n    box-shadow: 0 1px 1px rgba(0, 0, 0, .05);\n    &.small {\n      max-width: 830px;\n      min-width: 700px;\n      margin-left: 20px;\n      margin-top: 10px;\n    }\n    header {\n      position: relative;\n      z-index: 10;\n      > .title {\n        margin: 0;\n        color: #333;\n        border-color: #ddd;\n        font-size: 18px;\n        font-weight: 300;\n        letter-spacing: 0.025em;\n        height: 60px;\n        line-height: 45px;\n        padding: 10px 15px;\n        border-bottom: 1px solid #eee;\n        border-top-left-radius: 3px;\n        border-top-right-radius: 3px;\n      }\n      > .header_right {\n        position: absolute;\n        top: 50%;\n        right: 20px;\n        transform: translate(0, -50%);\n      }\n    }\n    .body {\n      padding: 15px;\n    }\n  }\n</style>\n<style lang=\"less\">\n  .panel-options {\n    background-color: transparent;\n    position: relative;\n    height: 50px;\n    button {\n      margin-top: 18px;\n      margin-right: 10px;\n    }\n    > .page {\n      position: absolute;\n      right:20px;\n      top: 20px;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/ScreenFull.vue",
    "content": "<template>\n  <svg @click='click' class=\"icon screenfull\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n    t=\"1497503607356\" viewBox=\"0 0 1024 1024\" version=\"1.1\" p-id=\"4109\" :fill='color' :width=\"width\" :height=\"height\">\n    <path d=\"M604.157933 512l204.484208 204.484208 82.942037-82.942037c10.364045-10.952446 26.498514-13.83817 40.309054-8.067746 13.249769 5.742794 22.465664 18.99154 22.465664 33.977859l0 258.042008c0 20.168342-16.695241 36.863582-36.863582 36.863582L659.452283 954.357873c-14.986319 0-28.236088-9.215896-33.977859-23.025413-5.770424-13.249769-2.885723-29.384237 8.067746-39.748283l82.942037-82.942037L512 604.157933 307.515792 808.642141l82.942037 82.942037c10.952446 10.364045 13.83817 26.498514 8.067746 39.748283-5.742794 13.809517-18.99154 23.025413-33.977859 23.025413L106.504686 954.357873c-20.168342 0-36.863582-16.695241-36.863582-36.863582L69.641103 659.452283c0-14.986319 9.215896-28.236088 23.025413-33.977859 13.249769-5.770424 29.384237-2.8847 39.748283 8.067746l82.942037 82.942037 204.484208-204.484208L215.357859 307.515792l-82.942037 82.942037c-6.890944 6.918573-16.10684 10.952446-25.911136 10.952446-4.593622 0-9.804297-1.14815-13.83817-2.8847-13.809517-5.742794-23.025413-18.99154-23.025413-33.977859L69.641103 106.504686c0-20.168342 16.695241-36.863582 36.863582-36.863582L364.546693 69.641103c14.986319 0 28.236088 9.215896 33.977859 23.025413 5.770424 13.249769 2.8847 29.384237-8.067746 39.748283l-82.942037 82.942037 204.484208 204.484208L716.484208 215.357859l-82.942037-82.942037c-10.952446-10.364045-13.83817-26.498514-8.067746-39.748283 5.742794-13.809517 18.99154-23.025413 33.977859-23.025413l258.042008 0c20.168342 0 36.863582 16.695241 36.863582 36.863582l0 258.042008c0 14.986319-9.215896 28.236088-22.465664 33.977859-4.593622 1.736551-9.804297 2.8847-14.397918 2.8847-9.804297 0-19.020192-4.033873-25.911136-10.952446l-82.942037-82.942037L604.157933 512z\"\n      p-id=\"4110\" />\n  </svg>\n</template>\n\n<script>\nimport screenfull from 'screenfull'\nexport default {\n  name: 'hamburger',\n  props: {\n    width: {\n      type: Number,\n      default: 22\n    },\n    height: {\n      type: Number,\n      default: 22\n    },\n    color: {\n      type: String,\n      default: '#5A5E66'\n    }\n  },\n  data () {\n    return {\n      isFullscreen: false\n    }\n  },\n  methods: {\n    click () {\n      if (!screenfull.enabled) {\n        this.$warning('Your browser doesn\\'t support fullscreen')\n        return false\n      }\n      screenfull.toggle()\n    }\n  }\n}\n</script>\n\n<style scoped>\n.screenfull {\n  display: inline-block;\n  cursor: pointer;\n  vertical-align: -0.15em;\n}\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/SideMenu.vue",
    "content": "<template>\n  <el-menu class=\"vertical_menu\"\n           :router=\"true\" :default-active=\"currentPath\">\n    <div class=\"logo\">\n      <img src=\"../../../assets/logo.svg\" alt=\"oj admin\"/>\n    </div>\n    <el-menu-item index=\"/\"><i class=\"el-icon-fa-dashboard\"></i>{{$t('m.Dashboard')}}</el-menu-item>\n    <el-submenu v-if=\"isSuperAdmin\" index=\"general\">\n      <template slot=\"title\"><i class=\"el-icon-menu\"></i>{{$t('m.General')}}</template>\n      <el-menu-item index=\"/user\">{{$t('m.User')}}</el-menu-item>\n      <el-menu-item index=\"/announcement\">{{$t('m.Announcement')}}</el-menu-item>\n      <el-menu-item index=\"/conf\">{{$t('m.System_Config')}}</el-menu-item>\n      <el-menu-item index=\"/judge-server\">{{$t('m.Judge_Server')}}</el-menu-item>\n      <el-menu-item index=\"/prune-test-case\">{{$t('m.Prune_Test_Case')}}</el-menu-item>\n    </el-submenu>\n    <el-submenu index=\"problem\" v-if=\"hasProblemPermission\">\n      <template slot=\"title\"><i class=\"el-icon-fa-bars\"></i>{{$t('m.Problem')}}</template>\n      <el-menu-item index=\"/problems\">{{$t('m.Problem_List')}}</el-menu-item>\n      <el-menu-item index=\"/problem/create\">{{$t('m.Create_Problem')}}</el-menu-item>\n      <el-menu-item index=\"/problem/batch_ops\">{{$t('m.Export_Import_Problem')}}</el-menu-item>\n\n    </el-submenu>\n    <el-submenu index=\"contest\">\n      <template slot=\"title\"><i class=\"el-icon-fa-trophy\"></i>{{$t('m.Contest')}}</template>\n      <el-menu-item index=\"/contest\">{{$t('m.Contest_List')}}</el-menu-item>\n      <el-menu-item index=\"/contest/create\">{{$t('m.Create_Contest')}}</el-menu-item>\n    </el-submenu>\n  </el-menu>\n</template>\n\n<script>\n  import {mapGetters} from 'vuex'\n\n  export default {\n    name: 'SideMenu',\n    data () {\n      return {\n        currentPath: ''\n      }\n    },\n    mounted () {\n      this.currentPath = this.$route.path\n    },\n    computed: {\n      ...mapGetters(['user', 'isSuperAdmin', 'hasProblemPermission'])\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .vertical_menu {\n    overflow: auto;\n    width: 205px;\n    height: 100%;\n    position: fixed !important;\n    z-index: 100;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    .logo {\n      margin: 20px 0;\n      text-align: center;\n      img {\n        background-color: #fff;\n        border-radius: 50%;\n        border: 3px solid #fff;\n        width: 75px;\n        height: 75px;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/Simditor.vue",
    "content": "<template>\n  <textarea ref=\"editor\"></textarea>\n</template>\n\n<script>\n  import Simditor from 'tar-simditor'\n  import 'tar-simditor/styles/simditor.css'\n  import 'tar-simditor-markdown'\n  import 'tar-simditor-markdown/styles/simditor-markdown.css'\n  import './simditor-file-upload'\n\n  export default {\n    name: 'Simditor',\n    props: {\n      toolbar: {\n        type: Array,\n        default: () => ['title', 'bold', 'italic', 'underline', 'fontScale', 'color', 'ol', 'ul', '|', 'blockquote', 'code', 'link', 'table', 'image', 'uploadfile', 'hr', '|', 'indent', 'outdent', 'alignment', '|', 'markdown']\n      },\n      value: {\n        type: String,\n        default: ''\n      }\n    },\n    data () {\n      return {\n        editor: null,\n        currentValue: this.value\n      }\n    },\n    mounted () {\n      this.editor = new Simditor({\n        textarea: this.$refs.editor,\n        toolbar: this.toolbar,\n        pasteImage: true,\n        markdown: false,\n        upload: {\n          url: '/api/admin/upload_image/',\n          params: null,\n          fileKey: 'image',\n          connectionCount: 3,\n          leaveConfirm: this.$i18n.t('m.Uploading_is_in_progress')\n        },\n        allowedStyles: {\n          span: ['color']\n        }\n      })\n      this.editor.on('valuechanged', (e, src) => {\n        this.currentValue = this.editor.getValue()\n      })\n      this.editor.on('decorate', (e, src) => {\n        this.currentValue = this.editor.getValue()\n      })\n\n      this.editor.setValue(this.value)\n    },\n    watch: {\n      'value' (val) {\n        if (this.currentValue !== val) {\n          this.currentValue = val\n          this.editor.setValue(val)\n        }\n      },\n      'currentValue' (newVal, oldVal) {\n        if (newVal !== oldVal) {\n          this.$emit('change', newVal)\n          this.$emit('input', newVal)\n        }\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/TopNav.vue",
    "content": "<template>\n  <div class=\"breadcrumb\">\n    <el-breadcrumb separator=\">\">\n      <el-breadcrumb-item :to=\"{ path: '/' }\">Home page</el-breadcrumb-item>\n      <el-breadcrumb-item><slot name=\"topNavName\">PLEASE OVERIDE ME</slot></el-breadcrumb-item>\n    </el-breadcrumb>\n  </div>\n</template>\n\n<style scoped>\n  .breadcrumb {\n    margin: 10px;\n    margin-right: 25px;\n    margin-bottom: 20px;\n    padding: 15px;\n    background-color: #fff;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/btn/Cancel.vue",
    "content": "<template>\n  <el-button plain type=\"primary\">{{$t('m.Cancel')}}</el-button>\n</template>\n<script>\n  export default{\n    name: 'Cancel'\n  }\n</script>\n"
  },
  {
    "path": "src/pages/admin/components/btn/IconBtn.vue",
    "content": "<template>\n  <div style=\"display: inline-block;\">\n    <el-tooltip class=\"item\" effect=\"dark\" :content=\"name\" placement=\"top\">\n      <el-button plain :icon=\"'el-icon-fa-' + icon\" size=\"mini\">\n      </el-button>\n    </el-tooltip>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'IconBtn',\n    props: {\n      name: {\n        type: String,\n        required: true\n      },\n      icon: {\n        type: String,\n        required: true\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/pages/admin/components/btn/Save.vue",
    "content": "<template>\n  <el-button type=\"primary\">{{$t('m.Save')}}</el-button>\n</template>\n<script>\n  export default{\n    name: 'Save'\n  }\n</script>\n"
  },
  {
    "path": "src/pages/admin/components/infoCard.vue",
    "content": "<template>\n  <el-card :body-style=\"{padding: 0, height: '100%'}\" class=\"info-card\">\n    <el-row type=\"flex\" class=\"info-card-container\">\n      <el-col :span=\"8\" :style=\"{'background-color': color}\" class=\"height-100\">\n        <i :class=\"['info-card-icon', icon]\" :style=\"{'font-size': iconSize}\"></i>\n      </el-col>\n      <el-col :span=\"16\" class=\"info-card-text\">\n        <p :style=\"textStyle\">{{value}}</p>\n        <p style=\"font-weight: 300;\">{{message}}</p>\n      </el-col>\n    </el-row>\n  </el-card>\n</template>\n\n<script>\n  export default {\n    name: 'inforCard',\n    props: {\n      value: [String, Number],\n      color: String,\n      message: String,\n      icon: String,\n      iconSize: {\n        type: String,\n        default: '35px'\n      },\n      countSize: {\n        type: String,\n        default: '25px'\n      },\n      countWeight: {\n        type: Number,\n        default: 700\n      }\n    },\n    computed: {\n      textStyle () {\n        return {\n          'font-size': this.countSize,\n          'font-weight': this.countWeight,\n          color: this.color\n        }\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  @card-height: 90px;\n  .height-100 {\n    height: 100%;\n  }\n\n  .info-card {\n    display: inline-block;\n    margin-right: 10px;\n    width: 250px;\n    height: @card-height;\n    text-align: center;\n    vertical-align: middle;\n\n    &-container {\n      height: 100%;\n      align-items: center;\n      justify-content: center;\n    }\n    &-icon {\n      line-height: @card-height;\n      color: white;\n    }\n    &-text {\n      p {\n        margin: 0;\n        .minor-text {\n          font-weight: 300;\n          margin-top: 2px;\n          font-size: 20px;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/components/simditor-file-upload.js",
    "content": "/* eslint-disable */\n\nimport Simditor from 'tar-simditor'\nimport * as $ from 'jquery'\n\nvar UploadFile,\n  __hasProp = {}.hasOwnProperty,\n  __extends = function (child, parent) {\n    for (var key in parent) {\n      if (__hasProp.call(parent, key)) child[key] = parent[key];\n    }\n\n    function ctor() {\n      this.constructor = child;\n    }\n\n    ctor.prototype = parent.prototype;\n    child.prototype = new ctor();\n    child.__super__ = parent.prototype;\n    return child;\n  },\n  __slice = [].slice;\n\nUploadFile = (function (_super) {\n  __extends(UploadFile, _super);\n\n  UploadFile.i18n = {\n    'zh-CN': {\n      uploadfile: '上传文件'\n    },\n    'en-US': {\n      uploadfile: 'upload file'\n    }\n  };\n\n  UploadFile.prototype.name = 'uploadfile';\n\n  UploadFile.prototype.icon = 'upload';\n\n  function UploadFile() {\n    var args;\n    args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];\n    UploadFile.__super__.constructor.apply(this, args);\n    this._initUpload();\n  }\n\n  UploadFile.prototype._initUpload = function () {\n    this.input = $('<input />', {\n      type: 'file',\n      style: 'position:absolute;top:0;right:0;height:100%;width:100%;opacity:0;filter:alpha(opacity=0);cursor:pointer;'\n    }).prependTo(this.el)\n    var _this = this;\n    this.el.on('click mousedown', 'input[type=file]', function (e) {\n      return e.stopPropagation();\n    }).on('change', 'input[type=file]', function (e) {\n\n      var formData = new FormData();\n      formData.append('file', this.files[0]);\n      $.ajax({\n        url: '/api/admin/upload_file',\n        type: 'POST',\n        cache: false,\n        data: formData,\n        processData: false,\n        contentType: false\n      }).done(function (res) {\n        if (!res.success) {\n          alert(\"upload file failed\")\n        } else {\n          let link = '<a target=\"_blank\" className=\"simditor-attach-link\" href=\"' + res.file_path + '\">' + res.file_name + '</a>'\n          _this.editor.setValue(_this.editor.getValue() + link)\n        }\n      }).fail(function (res) {\n        alert(\"upload file failed\")\n      });\n    });\n  }\n\n  return UploadFile;\n\n})(Simditor.Button);\n\nSimditor.Toolbar.addButton(UploadFile);\n\n\n"
  },
  {
    "path": "src/pages/admin/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>OnlineJudge</title>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\"/>\n  <meta name=\"renderer\" content=\"webkit\"/>\n  <link rel=\"shortcut icon\" href=\"/public/website/favicon.ico\">\n</head>\n<body>\n<div id=\"app\"></div>\n<!-- built files will be auto injected -->\n</body>\n</html>\n"
  },
  {
    "path": "src/pages/admin/index.js",
    "content": "import 'babel-polyfill'\nimport Vue from 'vue'\nimport App from './App.vue'\nimport store from '@/store'\nimport i18n from '@/i18n'\nimport Element from 'element-ui'\nimport 'element-ui/lib/theme-chalk/index.css'\n\nimport filters from '@/utils/filters'\nimport router from './router'\nimport { GOOGLE_ANALYTICS_ID } from '@/utils/constants'\nimport VueAnalytics from 'vue-analytics'\nimport katex from '@/plugins/katex'\n\nimport Panel from './components/Panel.vue'\nimport IconBtn from './components/btn/IconBtn.vue'\nimport Save from './components/btn/Save.vue'\nimport Cancel from './components/btn/Cancel.vue'\nimport './style.less'\n\n// register global utility filters.\nObject.keys(filters).forEach(key => {\n  Vue.filter(key, filters[key])\n})\n\nVue.use(VueAnalytics, {\n  id: GOOGLE_ANALYTICS_ID,\n  router\n})\nVue.use(katex)\nVue.component(IconBtn.name, IconBtn)\nVue.component(Panel.name, Panel)\nVue.component(Save.name, Save)\nVue.component(Cancel.name, Cancel)\n\nVue.use(Element, {\n  i18n: (key, value) => i18n.t(key, value)\n})\n\nVue.prototype.$error = (msg) => {\n  Vue.prototype.$message({'message': msg, 'type': 'error'})\n}\n\nVue.prototype.$warning = (msg) => {\n  Vue.prototype.$message({'message': msg, 'type': 'warning'})\n}\n\nVue.prototype.$success = (msg) => {\n  if (!msg) {\n    Vue.prototype.$message({'message': 'Succeeded', 'type': 'success'})\n  } else {\n    Vue.prototype.$message({'message': msg, 'type': 'success'})\n  }\n}\n\nnew Vue(Vue.util.extend({router, store, i18n}, App)).$mount('#app')\n"
  },
  {
    "path": "src/pages/admin/router.js",
    "content": "import Vue from 'vue'\nimport VueRouter from 'vue-router'\n// 引入 view 组件\nimport { Announcement, Conf, Contest, ContestList, Home, JudgeServer, Login,\n  Problem, ProblemList, User, PruneTestCase, Dashboard, ProblemImportOrExport } from './views'\nVue.use(VueRouter)\n\nexport default new VueRouter({\n  mode: 'history',\n  base: '/admin/',\n  scrollBehavior: () => ({y: 0}),\n  routes: [\n    {\n      path: '/login',\n      name: 'login',\n      component: Login\n    },\n    {\n      path: '/',\n      component: Home,\n      children: [\n        {\n          path: '',\n          name: 'dashboard',\n          component: Dashboard\n        },\n        {\n          path: '/announcement',\n          name: 'announcement',\n          component: Announcement\n        },\n        {\n          path: '/user',\n          name: 'user',\n          component: User\n        },\n        {\n          path: '/conf',\n          name: 'conf',\n          component: Conf\n        },\n        {\n          path: '/judge-server',\n          name: 'judge-server',\n          component: JudgeServer\n        },\n        {\n          path: '/prune-test-case',\n          name: 'prune-test-case',\n          component: PruneTestCase\n        },\n        {\n          path: '/problems',\n          name: 'problem-list',\n          component: ProblemList\n        },\n        {\n          path: '/problem/create',\n          name: 'create-problem',\n          component: Problem\n        },\n        {\n          path: '/problem/edit/:problemId',\n          name: 'edit-problem',\n          component: Problem\n        },\n        {\n          path: '/problem/batch_ops',\n          name: 'problem_batch_ops',\n          component: ProblemImportOrExport\n        },\n        {\n          path: '/contest/create',\n          name: 'create-contest',\n          component: Contest\n        },\n        {\n          path: '/contest',\n          name: 'contest-list',\n          component: ContestList\n        },\n        {\n          path: '/contest/:contestId/edit',\n          name: 'edit-contest',\n          component: Contest\n        },\n        {\n          path: '/contest/:contestId/announcement',\n          name: 'contest-announcement',\n          component: Announcement\n        },\n        {\n          path: '/contest/:contestId/problems',\n          name: 'contest-problem-list',\n          component: ProblemList\n        },\n        {\n          path: '/contest/:contestId/problem/create',\n          name: 'create-contest-problem',\n          component: Problem\n        },\n        {\n          path: '/contest/:contestId/problem/:problemId/edit',\n          name: 'edit-contest-problem',\n          component: Problem\n        }\n      ]\n    },\n    {\n      path: '*', redirect: '/login'\n    }\n  ]\n})\n"
  },
  {
    "path": "src/pages/admin/style.less",
    "content": "[class^=\"el-icon-fa\"], [class*=\" el-icon-fa\"] {\n  font-family: FontAwesome !important;\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1;\n  display: inline-block;\n  text-rendering: auto;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n@import url(\"../../../node_modules/font-awesome/less/font-awesome\");\n@fa-css-prefix: el-icon-fa;\n"
  },
  {
    "path": "src/pages/admin/views/Home.vue",
    "content": "<template>\n  <div class=\"container\">\n    <div>\n      <SideMenu></SideMenu>\n    </div>\n    <div id=\"header\">\n      <i class=\"el-icon-fa-font katex-editor\" @click=\"katexVisible=true\" ></i>\n      <screen-full :width=\"14\" :height=\"14\" class=\"screen-full\"></screen-full>\n      <el-dropdown @command=\"handleCommand\">\n        <span>{{user.username}}<i class=\"el-icon-caret-bottom el-icon--right\"></i></span>\n        <el-dropdown-menu slot=\"dropdown\">\n          <el-dropdown-item command=\"logout\">Logout</el-dropdown-item>\n        </el-dropdown-menu>\n      </el-dropdown>\n    </div>\n    <div class=\"content-app\">\n      <transition name=\"fadeInUp\" mode=\"out-in\">\n        <router-view></router-view>\n      </transition>\n      <div class=\"footer\">\n        Build Version: {{ version }}\n      </div>\n    </div>\n\n    <el-dialog :title=\"$t('m.Latex_Editor')\" :visible.sync=\"katexVisible\">\n      <KatexEditor></KatexEditor>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import { types } from '@/store'\n  import { mapGetters } from 'vuex'\n  import SideMenu from '../components/SideMenu.vue'\n  import ScreenFull from '@admin/components/ScreenFull.vue'\n  import KatexEditor from '@admin/components/KatexEditor.vue'\n  import api from '../api'\n\n  export default {\n    name: 'app',\n    data () {\n      return {\n        version: process.env.VERSION,\n        katexVisible: false\n      }\n    },\n    components: {\n      SideMenu,\n      KatexEditor,\n      ScreenFull\n    },\n    beforeRouteEnter (to, from, next) {\n      api.getProfile().then(res => {\n        if (!res.data.data) {\n          // not login\n          next({name: 'login'})\n        } else {\n          next(vm => {\n            vm.$store.commit(types.CHANGE_PROFILE, {profile: res.data.data})\n          })\n        }\n      })\n    },\n    methods: {\n      handleCommand (command) {\n        if (command === 'logout') {\n          api.logout().then(() => {\n            this.$router.push({name: 'login'})\n          })\n        }\n      }\n    },\n    computed: {\n      ...mapGetters(['user'])\n    }\n  }\n</script>\n\n<style lang=\"less\">\n  a {\n    background-color: transparent;\n  }\n\n  a:active, a:hover {\n    outline-width: 0\n  }\n\n  img {\n    border-style: none\n  }\n\n  .container {\n    overflow: auto;\n    font-weight: 400;\n    height: 100%;\n    -webkit-font-smoothing: antialiased;\n    background-color: #EDECEC;\n    overflow-y: scroll;\n    min-width: 1000px;\n  }\n\n  * {\n    box-sizing: border-box;\n  }\n\n  #header {\n    text-align: right;\n    padding-left: 210px;\n    padding-right: 30px;\n    line-height: 50px;\n    height: 50px;\n    background: #F9FAFC;\n    .screen-full {\n      margin-right: 8px;\n    }\n  }\n\n  .content-app {\n    padding-top: 20px;\n    padding-right: 10px;\n    padding-left: 210px;\n  }\n\n  .footer {\n    margin: 15px;\n    text-align: center;\n    font-size: small;\n  }\n\n  @keyframes fadeInUp {\n    from {\n      opacity: 0;\n      transform: translate(0, 30px);\n    }\n\n    to {\n      opacity: 1;\n      transform: none;\n    }\n  }\n\n  .fadeInUp-enter-active {\n    animation: fadeInUp .8s;\n  }\n\n  .katex-editor {\n    margin-right: 5px;\n    /*font-size: 18px;*/\n  }\n\n\n\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/contest/Contest.vue",
    "content": "<template>\n  <div class=\"view\">\n    <Panel :title=\"title\">\n      <el-form label-position=\"top\">\n        <el-row :gutter=\"20\">\n          <el-col :span=\"24\">\n            <el-form-item :label=\"$t('m.ContestTitle')\" required>\n              <el-input v-model=\"contest.title\" :placeholder=\"$t('m.ContestTitle')\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item :label=\"$t('m.ContestDescription')\" required>\n              <Simditor v-model=\"contest.description\"></Simditor>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Contest_Start_Time')\" required>\n              <el-date-picker\n                v-model=\"contest.start_time\"\n                type=\"datetime\"\n                :placeholder=\"$t('m.Contest_Start_Time')\">\n              </el-date-picker>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Contest_End_Time')\" required>\n              <el-date-picker\n                v-model=\"contest.end_time\"\n                type=\"datetime\"\n                :placeholder=\"$t('m.Contest_End_Time')\">\n              </el-date-picker>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Contest_Password')\">\n              <el-input v-model=\"contest.password\" :placeholder=\"$t('m.Contest_Password')\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Contest_Rule_Type')\">\n              <el-radio class=\"radio\" v-model=\"contest.rule_type\" label=\"ACM\" :disabled=\"disableRuleType\">ACM</el-radio>\n              <el-radio class=\"radio\" v-model=\"contest.rule_type\" label=\"OI\" :disabled=\"disableRuleType\">OI</el-radio>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Real_Time_Rank')\">\n              <el-switch\n                v-model=\"contest.real_time_rank\"\n                active-color=\"#13ce66\"\n                inactive-color=\"#ff4949\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Contest_Status')\">\n              <el-switch\n                v-model=\"contest.visible\"\n                active-text=\"\"\n                inactive-text=\"\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item :label=\"$t('m.Allowed_IP_Ranges')\">\n              <div v-for=\"(range, index) in contest.allowed_ip_ranges\" :key=\"index\">\n                <el-row :gutter=\"20\" style=\"margin-bottom: 15px\">\n                  <el-col :span=\"8\">\n                    <el-input v-model=\"range.value\" :placeholder=\"$t('m.CIDR_Network')\"></el-input>\n                  </el-col>\n                  <el-col :span=\"10\">\n                    <el-button plain icon=\"el-icon-fa-plus\" @click=\"addIPRange\"></el-button>\n                    <el-button plain icon=\"el-icon-fa-trash\" @click=\"removeIPRange(range)\"></el-button>\n                  </el-col>\n                </el-row>\n              </div>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <save @click.native=\"saveContest\"></save>\n    </Panel>\n  </div>\n</template>\n\n<script>\n  import api from '../../api.js'\n  import Simditor from '../../components/Simditor.vue'\n\n  export default {\n    name: 'CreateContest',\n    components: {\n      Simditor\n    },\n    data () {\n      return {\n        title: 'Create Contest',\n        disableRuleType: false,\n        contest: {\n          title: '',\n          description: '',\n          start_time: '',\n          end_time: '',\n          rule_type: 'ACM',\n          password: '',\n          real_time_rank: true,\n          visible: true,\n          allowed_ip_ranges: [{\n            value: ''\n          }]\n        }\n      }\n    },\n    methods: {\n      saveContest () {\n        let funcName = this.$route.name === 'edit-contest' ? 'editContest' : 'createContest'\n        let data = Object.assign({}, this.contest)\n        let ranges = []\n        for (let v of data.allowed_ip_ranges) {\n          if (v.value !== '') {\n            ranges.push(v.value)\n          }\n        }\n        data.allowed_ip_ranges = ranges\n        api[funcName](data).then(res => {\n          this.$router.push({name: 'contest-list', query: {refresh: 'true'}})\n        }).catch(() => {\n        })\n      },\n      addIPRange () {\n        this.contest.allowed_ip_ranges.push({value: ''})\n      },\n      removeIPRange (range) {\n        let index = this.contest.allowed_ip_ranges.indexOf(range)\n        if (index !== -1) {\n          this.contest.allowed_ip_ranges.splice(index, 1)\n        }\n      }\n    },\n    mounted () {\n      if (this.$route.name === 'edit-contest') {\n        this.title = 'Edit Contest'\n        this.disableRuleType = true\n        api.getContest(this.$route.params.contestId).then(res => {\n          let data = res.data.data\n          let ranges = []\n          for (let v of data.allowed_ip_ranges) {\n            ranges.push({value: v})\n          }\n          if (ranges.length === 0) {\n            ranges.push({value: ''})\n          }\n          data.allowed_ip_ranges = ranges\n          this.contest = data\n        }).catch(() => {\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/pages/admin/views/contest/ContestList.vue",
    "content": "<template>\n  <div class=\"view\">\n    <Panel title=\"Contest List\">\n      <div slot=\"header\">\n        <el-input\n          v-model=\"keyword\"\n          prefix-icon=\"el-icon-search\"\n          placeholder=\"Keywords\">\n        </el-input>\n      </div>\n      <el-table\n        v-loading=\"loading\"\n        element-loading-text=\"loading\"\n        ref=\"table\"\n        :data=\"contestList\"\n        style=\"width: 100%\">\n        <el-table-column type=\"expand\">\n          <template slot-scope=\"props\">\n            <p>Start Time: {{props.row.start_time | localtime }}</p>\n            <p>End Time: {{props.row.end_time | localtime }}</p>\n            <p>Create Time: {{props.row.create_time | localtime}}</p>\n            <p>Creator: {{props.row.created_by.username}}</p>\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"id\"\n          width=\"80\"\n          label=\"ID\">\n        </el-table-column>\n        <el-table-column\n          prop=\"title\"\n          label=\"Title\">\n        </el-table-column>\n        <el-table-column\n          label=\"Rule Type\"\n          width=\"130\">\n          <template slot-scope=\"scope\">\n            <el-tag type=\"gray\">{{scope.row.rule_type}}</el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column\n          label=\"Contest Type\"\n          width=\"180\">\n          <template slot-scope=\"scope\">\n            <el-tag :type=\"scope.row.contest_type === 'Public' ? 'success' : 'primary'\">\n              {{ scope.row.contest_type}}\n            </el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column\n          label=\"Status\"\n          width=\"130\">\n          <template slot-scope=\"scope\">\n            <el-tag\n              :type=\"scope.row.status === '-1' ? 'danger' : scope.row.status === '0' ? 'success' : 'primary'\">\n              {{ scope.row.status | contestStatus}}\n            </el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column\n          width=\"100\"\n          label=\"Visible\">\n          <template slot-scope=\"scope\">\n            <el-switch v-model=\"scope.row.visible\"\n                       active-text=\"\"\n                       inactive-text=\"\"\n                       @change=\"handleVisibleSwitch(scope.row)\">\n            </el-switch>\n          </template>\n        </el-table-column>\n        <el-table-column\n          fixed=\"right\"\n          width=\"250\"\n          label=\"Operation\">\n          <div slot-scope=\"scope\">\n            <icon-btn name=\"Edit\" icon=\"edit\" @click.native=\"goEdit(scope.row.id)\"></icon-btn>\n            <icon-btn name=\"Problem\" icon=\"list-ol\" @click.native=\"goContestProblemList(scope.row.id)\"></icon-btn>\n            <icon-btn name=\"Announcement\" icon=\"info-circle\"\n                      @click.native=\"goContestAnnouncement(scope.row.id)\"></icon-btn>\n            <icon-btn icon=\"download\" name=\"Download Accepted Submissions\"\n                      @click.native=\"openDownloadOptions(scope.row.id)\"></icon-btn>\n          </div>\n        </el-table-column>\n      </el-table>\n      <div class=\"panel-options\">\n        <el-pagination\n          class=\"page\"\n          layout=\"prev, pager, next\"\n          @current-change=\"currentChange\"\n          :page-size=\"pageSize\"\n          :total=\"total\">\n        </el-pagination>\n      </div>\n    </Panel>\n    <el-dialog title=\"Download Contest Submissions\"\n               width=\"30%\"\n               :visible.sync=\"downloadDialogVisible\">\n      <el-switch v-model=\"excludeAdmin\" active-text=\"Exclude admin submissions\"></el-switch>\n      <span slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"downloadSubmissions\">确 定</el-button>\n      </span>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import api from '../../api.js'\n  import utils from '@/utils/utils'\n  import {CONTEST_STATUS_REVERSE} from '@/utils/constants'\n\n  export default {\n    name: 'ContestList',\n    data () {\n      return {\n        pageSize: 10,\n        total: 0,\n        contestList: [],\n        keyword: '',\n        loading: false,\n        excludeAdmin: true,\n        currentPage: 1,\n        currentId: 1,\n        downloadDialogVisible: false\n      }\n    },\n    mounted () {\n      this.getContestList(this.currentPage)\n    },\n    filters: {\n      contestStatus (value) {\n        return CONTEST_STATUS_REVERSE[value].name\n      }\n    },\n    methods: {\n      // 切换页码回调\n      currentChange (page) {\n        this.currentPage = page\n        this.getContestList(page)\n      },\n      getContestList (page) {\n        this.loading = true\n        api.getContestList((page - 1) * this.pageSize, this.pageSize, this.keyword).then(res => {\n          this.loading = false\n          this.total = res.data.data.total\n          this.contestList = res.data.data.results\n        }, res => {\n          this.loading = false\n        })\n      },\n      openDownloadOptions (contestId) {\n        this.downloadDialogVisible = true\n        this.currentId = contestId\n      },\n      downloadSubmissions () {\n        let excludeAdmin = this.excludeAdmin ? '1' : '0'\n        let url = `/admin/download_submissions?contest_id=${this.currentId}&exclude_admin=${excludeAdmin}`\n        utils.downloadFile(url)\n      },\n      goEdit (contestId) {\n        this.$router.push({name: 'edit-contest', params: {contestId}})\n      },\n      goContestAnnouncement (contestId) {\n        this.$router.push({name: 'contest-announcement', params: {contestId}})\n      },\n      goContestProblemList (contestId) {\n        this.$router.push({name: 'contest-problem-list', params: {contestId}})\n      },\n      handleVisibleSwitch (row) {\n        api.editContest(row)\n      }\n    },\n    watch: {\n      'keyword' () {\n        this.currentChange(1)\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/pages/admin/views/general/Announcement.vue",
    "content": "<template>\n  <div class=\"announcement view\">\n    <Panel :title=\"$t('m.General_Announcement')\">\n      <div class=\"list\">\n        <el-table\n          v-loading=\"loading\"\n          element-loading-text=\"loading\"\n          ref=\"table\"\n          :data=\"announcementList\"\n          style=\"width: 100%\">\n          <el-table-column\n            width=\"100\"\n            prop=\"id\"\n            label=\"ID\">\n          </el-table-column>\n          <el-table-column\n            prop=\"title\"\n            label=\"Title\">\n          </el-table-column>\n          <el-table-column\n            prop=\"create_time\"\n            label=\"CreateTime\">\n            <template slot-scope=\"scope\">\n              {{ scope.row.create_time | localtime }}\n            </template>\n          </el-table-column>\n          <el-table-column\n            prop=\"last_update_time\"\n            label=\"LastUpdateTime\">\n            <template slot-scope=\"scope\">\n              {{scope.row.last_update_time | localtime }}\n            </template>\n          </el-table-column>\n          <el-table-column\n            prop=\"created_by.username\"\n            label=\"Author\">\n          </el-table-column>\n          <el-table-column\n            width=\"100\"\n            prop=\"visible\"\n            label=\"Visible\">\n            <template slot-scope=\"scope\">\n              <el-switch v-model=\"scope.row.visible\"\n                         active-text=\"\"\n                         inactive-text=\"\"\n                         @change=\"handleVisibleSwitch(scope.row)\">\n              </el-switch>\n            </template>\n          </el-table-column>\n          <el-table-column\n            fixed=\"right\"\n            label=\"Option\"\n            width=\"200\">\n            <div slot-scope=\"scope\">\n              <icon-btn name=\"Edit\" icon=\"edit\" @click.native=\"openAnnouncementDialog(scope.row.id)\"></icon-btn>\n              <icon-btn name=\"Delete\" icon=\"trash\" @click.native=\"deleteAnnouncement(scope.row.id)\"></icon-btn>\n            </div>\n          </el-table-column>\n        </el-table>\n        <div class=\"panel-options\">\n          <el-button type=\"primary\" size=\"small\" @click=\"openAnnouncementDialog(null)\" icon=\"el-icon-plus\">Create</el-button>\n          <el-pagination\n            v-if=\"!contestID\"\n            class=\"page\"\n            layout=\"prev, pager, next\"\n            @current-change=\"currentChange\"\n            :page-size=\"pageSize\"\n            :total=\"total\">\n          </el-pagination>\n        </div>\n      </div>\n    </Panel>\n    <!--对话框-->\n    <el-dialog :title=\"announcementDialogTitle\" :visible.sync=\"showEditAnnouncementDialog\"\n               @open=\"onOpenEditDialog\" :close-on-click-modal=\"false\">\n      <el-form label-position=\"top\">\n        <el-form-item :label=\"$t('m.Announcement_Title')\" required>\n          <el-input\n            v-model=\"announcement.title\"\n            :placeholder=\"$t('m.Announcement_Title')\" class=\"title-input\">\n          </el-input>\n        </el-form-item>\n        <el-form-item :label=\"$t('m.Announcement_Content')\" required>\n          <Simditor v-model=\"announcement.content\"></Simditor>\n        </el-form-item>\n        <div class=\"visible-box\">\n          <span>{{$t('m.Announcement_visible')}}</span>\n          <el-switch\n            v-model=\"announcement.visible\"\n            active-text=\"\"\n            inactive-text=\"\">\n          </el-switch>\n        </div>\n      </el-form>\n      <span slot=\"footer\" class=\"dialog-footer\">\n          <cancel @click.native=\"showEditAnnouncementDialog = false\"></cancel>\n          <save type=\"primary\" @click.native=\"submitAnnouncement\"></save>\n        </span>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import Simditor from '../../components/Simditor.vue'\n  import api from '../../api.js'\n\n  export default {\n    name: 'Announcement',\n    components: {\n      Simditor\n    },\n    data () {\n      return {\n        contestID: '',\n        // 显示编辑公告对话框\n        showEditAnnouncementDialog: false,\n        // 公告列表\n        announcementList: [],\n        // 一页显示的公告数\n        pageSize: 15,\n        // 总公告数\n        total: 0,\n        // 当前公告id\n        currentAnnouncementId: null,\n        mode: 'create',\n        // 公告 (new | edit) model\n        announcement: {\n          title: '',\n          visible: true,\n          content: ''\n        },\n        // 对话框标题\n        announcementDialogTitle: 'Edit Announcement',\n        // 是否显示loading\n        loading: true,\n        // 当前页码\n        currentPage: 0\n      }\n    },\n    mounted () {\n      this.init()\n    },\n    methods: {\n      init () {\n        this.contestID = this.$route.params.contestId\n        if (this.contestID) {\n          this.getContestAnnouncementList()\n        } else {\n          this.getAnnouncementList(1)\n        }\n      },\n      // 切换页码回调\n      currentChange (page) {\n        this.currentPage = page\n        this.getAnnouncementList(page)\n      },\n      getAnnouncementList (page) {\n        this.loading = true\n        api.getAnnouncementList((page - 1) * this.pageSize, this.pageSize).then(res => {\n          this.loading = false\n          this.total = res.data.data.total\n          this.announcementList = res.data.data.results\n        }, res => {\n          this.loading = false\n        })\n      },\n      getContestAnnouncementList () {\n        this.loading = true\n        api.getContestAnnouncementList(this.contestID).then(res => {\n          this.loading = false\n          this.announcementList = res.data.data\n        }).catch(() => {\n          this.loading = false\n        })\n      },\n      // 打开编辑对话框的回调\n      onOpenEditDialog () {\n        // todo 优化\n        // 暂时解决 文本编辑器显示异常bug\n        setTimeout(() => {\n          if (document.createEvent) {\n            let event = document.createEvent('HTMLEvents')\n            event.initEvent('resize', true, true)\n            window.dispatchEvent(event)\n          } else if (document.createEventObject) {\n            window.fireEvent('onresize')\n          }\n        }, 0)\n      },\n      // 提交编辑\n      // 默认传入MouseEvent\n      submitAnnouncement (data = undefined) {\n        let funcName = ''\n        if (!data.title) {\n          data = {\n            id: this.currentAnnouncementId,\n            title: this.announcement.title,\n            content: this.announcement.content,\n            visible: this.announcement.visible\n          }\n        }\n        if (this.contestID) {\n          data.contest_id = this.contestID\n          funcName = this.mode === 'edit' ? 'updateContestAnnouncement' : 'createContestAnnouncement'\n        } else {\n          funcName = this.mode === 'edit' ? 'updateAnnouncement' : 'createAnnouncement'\n        }\n        api[funcName](data).then(res => {\n          this.showEditAnnouncementDialog = false\n          this.init()\n        }).catch()\n      },\n      // 删除公告\n      deleteAnnouncement (announcementId) {\n        this.$confirm('Are you sure you want to delete this announcement?', 'Warning', {\n          confirmButtonText: 'Delete',\n          cancelButtonText: 'Cancel',\n          type: 'warning'\n        }).then(() => {\n          // then 为确定\n          this.loading = true\n          let funcName = this.contestID ? 'deleteContestAnnouncement' : 'deleteAnnouncement'\n          api[funcName](announcementId).then(res => {\n            this.loading = true\n            this.init()\n          })\n        }).catch(() => {\n          // catch 为取消\n          this.loading = false\n        })\n      },\n      openAnnouncementDialog (id) {\n        this.showEditAnnouncementDialog = true\n        if (id !== null) {\n          this.currentAnnouncementId = id\n          this.announcementDialogTitle = 'Edit Announcement'\n          this.announcementList.find(item => {\n            if (item.id === this.currentAnnouncementId) {\n              this.announcement.title = item.title\n              this.announcement.visible = item.visible\n              this.announcement.content = item.content\n              this.mode = 'edit'\n            }\n          })\n        } else {\n          this.announcementDialogTitle = 'Create Announcement'\n          this.announcement.title = ''\n          this.announcement.visible = true\n          this.announcement.content = ''\n          this.mode = 'create'\n        }\n      },\n      handleVisibleSwitch (row) {\n        this.mode = 'edit'\n        this.submitAnnouncement({\n          id: row.id,\n          title: row.title,\n          content: row.content,\n          visible: row.visible\n        })\n      }\n    },\n    watch: {\n      $route () {\n        this.init()\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .title-input {\n    margin-bottom: 20px;\n  }\n\n  .visible-box {\n    margin-top: 10px;\n    width: 205px;\n    float: left;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/general/Conf.vue",
    "content": "<template>\n  <div class=\"view\">\n    <Panel :title=\"$t('m.SMTP_Config')\">\n      <el-form label-position=\"left\" label-width=\"70px\" :model=\"smtp\">\n        <el-row :gutter=\"20\">\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.Server')\" required>\n              <el-input v-model=\"smtp.server\" placeholder=\"SMTP Server Address\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.Port')\" required>\n              <el-input type=\"number\" v-model=\"smtp.port\" placeholder=\"SMTP Server Port\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.Email')\" required>\n              <el-input v-model=\"smtp.email\" placeholder=\"Account Used To Send Email\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.Password')\" label-width=\"90px\" required>\n              <el-input v-model=\"smtp.password\" type=\"password\" placeholder=\"SMTP Server Password\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item label=\"TLS\">\n              <el-switch\n                v-model=\"smtp.tls\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <el-button type=\"primary\" @click=\"saveSMTPConfig\">Save</el-button>\n      <el-button type=\"warning\" @click=\"testSMTPConfig\"\n                 v-if=\"saved\" :loading=\"loadingBtnTest\">Send Test Email</el-button>\n    </Panel>\n\n    <Panel :title=\"$t('m.Website_Config')\">\n      <el-form label-position=\"left\" label-width=\"100px\" ref=\"form\" :model=\"websiteConfig\">\n        <el-row :gutter=\"20\">\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Base_Url')\" required>\n              <el-input v-model=\"websiteConfig.website_base_url\" placeholder=\"Website Base Url\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Name')\" required>\n              <el-input v-model=\"websiteConfig.website_name\" placeholder=\"Website Name\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Shortcut')\" required>\n              <el-input v-model=\"websiteConfig.website_name_shortcut\" placeholder=\"Website Name Shortcut\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item :label=\"$t('m.Footer')\" required>\n              <el-input type=\"textarea\" :autosize=\"{ minRows: 2, maxRows: 4}\" v-model=\"websiteConfig.website_footer\"\n                        placeholder=\"Website Footer HTML\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-col :span=\"12\">\n              <el-form-item :label=\"$t('m.Allow_Register')\" label-width=\"200px\">\n                <el-switch\n                  v-model=\"websiteConfig.allow_register\"\n                  active-color=\"#13ce66\"\n                  inactive-color=\"#ff4949\">\n                </el-switch>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"12\">\n              <el-form-item :label=\"$t('m.Submission_List_Show_All')\" label-width=\"200px\">\n                <el-switch\n                  v-model=\"websiteConfig.submission_list_show_all\"\n                  active-color=\"#13ce66\"\n                  inactive-color=\"#ff4949\">\n                </el-switch>\n              </el-form-item>\n            </el-col>\n          </el-col>\n        </el-row>\n      </el-form>\n      <save @click.native=\"saveWebsiteConfig\"></save>\n    </Panel>\n  </div>\n</template>\n\n<script>\n  import api from '../../api.js'\n\n  export default {\n    name: 'Conf',\n    data () {\n      return {\n        init: false,\n        saved: false,\n        loadingBtnTest: false,\n        smtp: {\n          server: 'smtp.example.com',\n          port: 25,\n          password: '',\n          email: 'email@example.com',\n          tls: true\n        },\n        websiteConfig: {}\n      }\n    },\n    mounted () {\n      api.getSMTPConfig().then(res => {\n        if (res.data.data) {\n          this.smtp = res.data.data\n        } else {\n          this.init = true\n          this.$warning('Please setup SMTP config at first')\n        }\n      })\n      api.getWebsiteConfig().then(res => {\n        this.websiteConfig = res.data.data\n      }).catch(() => {\n      })\n    },\n    methods: {\n      saveSMTPConfig () {\n        if (!this.init) {\n          api.editSMTPConfig(this.smtp).then(() => {\n            this.saved = true\n          }, () => {\n          })\n        } else {\n          api.createSMTPConfig(this.smtp).then(() => {\n            this.saved = true\n          }, () => {\n          })\n        }\n      },\n      testSMTPConfig () {\n        this.$prompt('Please input your email', '', {\n          inputPattern: /[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?/,\n          inputErrorMessage: 'Error email format'\n        }).then(({value}) => {\n          this.loadingBtnTest = true\n          api.testSMTPConfig(value).then(() => {\n            this.loadingBtnTest = false\n          }, () => {\n            this.loadingBtnTest = false\n          })\n        }).catch(() => {\n        })\n      },\n      saveWebsiteConfig () {\n        api.editWebsiteConfig(this.websiteConfig).then(() => {\n        }).catch(() => {\n        })\n      }\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/pages/admin/views/general/Dashboard.vue",
    "content": "<template>\n  <el-row type=\"flex\" :gutter=\"20\">\n    <el-col :md=\"10\" :lg=\"8\">\n      <el-card class=\"admin-info\">\n        <el-row :gutter=\"20\">\n          <el-col :span=\"10\">\n            <img class=\"avatar\" :src=\"profile.avatar\"/>\n          </el-col>\n          <el-col :span=\"14\">\n            <p class=\"admin-info-name\">{{user.username}}</p>\n            <p>{{user.admin_type}}</p>\n          </el-col>\n        </el-row>\n        <hr/>\n        <div class=\"last-info\">\n          <p class=\"last-info-title\">{{$t('m.Last_Login')}}</p>\n          <el-form label-width=\"80px\" class=\"last-info-body\">\n            <el-form-item label=\"Time:\">\n              <span>{{session.last_activity | localtime}}</span>\n            </el-form-item>\n            <el-form-item label=\"IP:\">\n              <span>{{session.ip}}</span>\n            </el-form-item>\n            <el-form-item label=\"OS\">\n              <span>{{os}}</span>\n            </el-form-item>\n            <el-form-item label=\"Browser:\">\n              <span>{{browser}}</span>\n            </el-form-item>\n          </el-form>\n        </div>\n      </el-card>\n      <panel :title=\"$t('m.System_Overview')\" v-if=\"isSuperAdmin\">\n        <p>{{$t('m.DashBoardJudge_Server')}}:  {{infoData.judge_server_count}}</p>\n        <p>{{$t('m.HTTPS_Status')}}:\n          <el-tag :type=\"https ? 'success' : 'danger'\" size=\"small\">\n            {{ https ? 'Enabled' : 'Disabled'}}\n          </el-tag>\n        </p>\n        <p>{{$t('m.Force_HTTPS')}}:\n          <el-tag :type=\"forceHttps ? 'success' : 'danger'\" size=\"small\">\n            {{forceHttps ? 'Enabled' : 'Disabled'}}\n          </el-tag>\n        </p>\n        <p>{{$t('m.CDN_HOST')}}:\n          <el-tag :type=\"cdn ? 'success' : 'warning'\" size=\"small\">\n            {{cdn ? cdn : 'Not Use'}}\n          </el-tag>\n        </p>\n      </panel>\n    </el-col>\n\n    <el-col :md=\"14\" :lg=\"16\" v-if=\"isSuperAdmin\">\n      <div class=\"info-container\">\n        <info-card color=\"#909399\" icon=\"el-icon-fa-users\" message=\"Total Users\" iconSize=\"30px\" class=\"info-item\"\n                   :value=\"infoData.user_count\"></info-card>\n        <info-card color=\"#67C23A\" icon=\"el-icon-fa-list\" message=\"Today Submissions\" class=\"info-item\"\n                   :value=\"infoData.today_submission_count\"></info-card>\n        <info-card color=\"#409EFF\" icon=\"el-icon-fa-trophy\" message=\"Recent Contests\" class=\"info-item\"\n                   :value=\"infoData.recent_contest_count\"></info-card>\n      </div>\n      <panel style=\"margin-top: 5px\">\n        <span slot=\"title\" v-loading=\"loadingReleases\">Release Notes\n        <el-popover placement=\"right\" trigger=\"hover\">\n          <i slot=\"reference\" class=\"el-icon-fa-question-circle import-user-icon\"></i>\n          <p>Please upgrade to the latest version to enjoy the new features. </p>\n          <p>Reference: <a href=\"http://docs.onlinejudge.me/#/onlinejudge/guide/upgrade\" target=\"_blank\">\n          http://docs.onlinejudge.me/#/onlinejudge/guide/upgrade</a>\n          </p>\n        </el-popover>\n        </span>\n\n        <el-collapse v-model=\"activeNames\" v-for=\"(release, index) of releases\" :key=\"'release' + index\">\n          <el-collapse-item :name=\"index+1\">\n            <template slot=\"title\">\n              <div v-if=\"release.new_version\">{{release.title}}\n                <el-tag size=\"mini\" type=\"success\">New Version</el-tag>\n              </div>\n              <span v-else>{{release.title}}</span>\n            </template>\n            <p>Level: {{release.level}}</p>\n            <p>Details: </p>\n            <div class=\"release-body\">\n              <ul v-for=\"detail in release.details\" :key=\"detail\">\n                <li v-html=\"detail\"></li>\n              </ul>\n            </div>\n          </el-collapse-item>\n        </el-collapse>\n      </panel>\n    </el-col>\n  </el-row>\n</template>\n\n\n<script>\n  import { mapGetters } from 'vuex'\n  import browserDetector from 'browser-detect'\n  import InfoCard from '@admin/components/infoCard.vue'\n  import api from '@admin/api'\n\n  export default {\n    name: 'dashboard',\n    components: {\n      InfoCard\n    },\n    data () {\n      return {\n        infoData: {\n          user_count: 0,\n          recent_contest_count: 0,\n          today_submission_count: 0,\n          judge_server_count: 0,\n          env: {}\n        },\n        activeNames: [1],\n        session: {},\n        loadingReleases: true,\n        releases: []\n      }\n    },\n    mounted () {\n      api.getDashboardInfo().then(resp => {\n        this.infoData = resp.data.data\n      }, () => {\n      })\n      api.getSessions().then(resp => {\n        this.parseSession(resp.data.data)\n      }, () => {\n      })\n      api.getReleaseNotes().then(resp => {\n        this.loadingReleases = false\n        let data = resp.data.data\n        if (!data) {\n          return\n        }\n        let currentVersion = data.local_version\n        data.update.forEach(release => {\n          if (release.version > currentVersion) {\n            release.new_version = true\n          }\n        })\n        this.releases = data.update\n      }, () => {\n        this.loadingReleases = false\n      })\n    },\n    methods: {\n      parseSession (sessions) {\n        let session = sessions[0]\n        if (sessions.length > 1) {\n          session = sessions.filter(s => !s.current_session).sort((a, b) => {\n            return a.last_activity < b.last_activity\n          })[0]\n        }\n        this.session = session\n      }\n    },\n    computed: {\n      ...mapGetters(['profile', 'user', 'isSuperAdmin']),\n      cdn () {\n        return this.infoData.env.STATIC_CDN_HOST\n      },\n      https () {\n        return document.URL.slice(0, 5) === 'https'\n      },\n      forceHttps () {\n        return this.infoData.env.FORCE_HTTPS\n      },\n      browser () {\n        let b = browserDetector(this.session.user_agent)\n        if (b.name && b.version) {\n          return b.name + ' ' + b.version\n        } else {\n          return 'Unknown'\n        }\n      },\n      os () {\n        let b = browserDetector(this.session.user_agent)\n        return b.os ? b.os : 'Unknown'\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\">\n  .admin-info {\n    margin-bottom: 20px;\n    &-name {\n      font-size: 24px;\n      font-weight: 700;\n      margin-bottom: 10px;\n      color: #409EFF;\n    }\n    .avatar {\n      max-width: 100%;\n    }\n    .last-info {\n      &-title {\n        font-size: 16px;\n      }\n      &-body {\n        .el-form-item {\n          margin-bottom: 5px;\n        }\n      }\n    }\n  }\n\n  .info-container {\n    display: flex;\n    justify-content: flex-start;\n    flex-wrap: wrap;\n    .info-item {\n      flex: 1 0 auto;\n      min-width: 200px;\n      margin-bottom: 10px;\n    }\n  }\n\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/general/JudgeServer.vue",
    "content": "<template>\n  <div class=\"view\">\n    <Panel :title=\"$t('m.Judge_Server_Token')\">\n      <code>{{ token }}</code>\n    </Panel>\n    <Panel :title=\"$t('m.Judge_Server_Info')\">\n      <el-table\n        :data=\"servers\"\n        :default-expand-all=\"true\"\n        border>\n        <el-table-column\n          type=\"expand\">\n          <template slot-scope=\"props\">\n            <p>{{$t('m.IP')}}:\n              <el-tag type=\"success\">{{ props.row.ip }}</el-tag>&nbsp;&nbsp;\n              {{$t('m.Judger_Version')}}:\n              <el-tag type=\"success\">{{ props.row.judger_version }}</el-tag>\n            </p>\n            <p>{{$t('m.Service_URL')}}: <code>{{ props.row.service_url }}</code></p>\n            <p>{{$t('m.Last_Heartbeat')}}: {{ props.row.last_heartbeat | localtime}}</p>\n            <p>{{$t('m.Create_Time')}}: {{ props.row.create_time | localtime }}</p>\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"status\"\n          label=\"Status\">\n          <template slot-scope=\"scope\">\n            <el-tag\n              :type=\"scope.row.status === 'normal' ? 'success' : 'danger'\">\n              {{ scope.row.status === 'normal' ? 'Normal' : 'Abnormal' }}\n            </el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"hostname\"\n          label=\"Hostname\">\n        </el-table-column>\n        <el-table-column\n          prop=\"task_number\"\n          label=\"Task Number\">\n        </el-table-column>\n        <el-table-column\n          prop=\"cpu_core\"\n          label=\"CPU Core\">\n        </el-table-column>\n        <el-table-column\n          prop=\"cpu_usage\"\n          label=\"CPU Usage\">\n          <template slot-scope=\"scope\">{{ scope.row.cpu_usage }}%</template>\n        </el-table-column>\n        <el-table-column\n          prop=\"memory_usage\"\n          label=\"Memory Usage\">\n          <template slot-scope=\"scope\">{{ scope.row.memory_usage }}%</template>\n        </el-table-column>\n        <el-table-column label=\"Disabled\">\n          <template slot-scope=\"{row}\">\n            <el-switch v-model=\"row.is_disabled\" @change=\"handleDisabledSwitch(row.id, row.is_disabled)\"></el-switch>\n          </template>\n        </el-table-column>\n        <el-table-column\n          fixed=\"right\"\n          label=\"Options\">\n          <template slot-scope=\"scope\">\n            <icon-btn name=\"Delete\" icon=\"trash\" @click.native=\"deleteJudgeServer(scope.row.hostname)\"></icon-btn>\n          </template>\n        </el-table-column>\n      </el-table>\n    </Panel>\n  </div>\n</template>\n\n<script>\n  import api from '../../api.js'\n\n  export default {\n    name: 'JudgeServer',\n    data () {\n      return {\n        servers: [],\n        token: '',\n        intervalId: -1\n      }\n    },\n    mounted () {\n      this.refreshJudgeServerList()\n      this.intervalId = setInterval(() => {\n        this.refreshJudgeServerList()\n      }, 5000)\n    },\n    methods: {\n      refreshJudgeServerList () {\n        api.getJudgeServer().then(res => {\n          this.servers = res.data.data.servers\n          this.token = res.data.data.token\n        })\n      },\n      deleteJudgeServer (hostname) {\n        this.$confirm('If you delete this judge server, it can\\'t be used until next heartbeat', 'Warning', {\n          confirmButtonText: 'Delete',\n          cancelButtonText: 'Cancel',\n          type: 'warning'\n        }).then(() => {\n          api.deleteJudgeServer(hostname).then(res =>\n            this.refreshJudgeServerList()\n          )\n        }).catch(() => {\n        })\n      },\n      handleDisabledSwitch (id, value) {\n        let data = {\n          id,\n          is_disabled: value\n        }\n        api.updateJudgeServer(data).catch(() => {})\n      }\n    },\n    beforeRouteLeave (to, from, next) {\n      clearInterval(this.intervalId)\n      next()\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/pages/admin/views/general/Login.vue",
    "content": "<template>\n  <el-form :model=\"ruleForm2\" :rules=\"rules2\" ref=\"ruleForm2\" label-position=\"left\" label-width=\"0px\"\n           class=\"demo-ruleForm login-container\">\n    <h3 class=\"title\">{{$t('m.Welcome_to_Login')}}</h3>\n    <el-form-item prop=\"account\">\n      <el-input type=\"text\" v-model=\"ruleForm2.account\" auto-complete=\"off\" :placeholder=\"$t('m.username')\" @keyup.enter.native=\"handleLogin\"></el-input>\n    </el-form-item>\n    <el-form-item prop=\"password\">\n      <el-input type=\"password\" v-model=\"ruleForm2.password\" auto-complete=\"off\" :placeholder=\"$t('m.password')\" @keyup.enter.native=\"handleLogin\"></el-input>\n    </el-form-item>\n    <el-form-item style=\"width:100%;\">\n      <el-button type=\"primary\" style=\"width:100%;\" @click.native.prevent=\"handleLogin\" :loading=\"logining\">{{$t('m.GO')}}\n      </el-button>\n    </el-form-item>\n  </el-form>\n</template>\n\n<script>\n  import api from '../../api'\n\n  export default {\n    data () {\n      return {\n        logining: false,\n        ruleForm2: {\n          account: '',\n          password: ''\n        },\n        rules2: {\n          account: [\n            {required: true, trigger: 'blur'}\n          ],\n          password: [\n            {required: true, trigger: 'blur'}\n          ]\n        },\n        checked: true\n      }\n    },\n    methods: {\n      handleLogin (ev) {\n        this.$refs.ruleForm2.validate((valid) => {\n          if (valid) {\n            this.logining = true\n            api.login(this.ruleForm2.account, this.ruleForm2.password).then(data => {\n              this.logining = false\n              this.$router.push({name: 'dashboard'})\n            }, () => {\n              this.logining = false\n            })\n          } else {\n            this.$error('Please check the error fields')\n          }\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .login-container {\n    /*box-shadow: 0 0px 8px 0 rgba(0, 0, 0, 0.06), 0 1px 0px 0 rgba(0, 0, 0, 0.02);*/\n    -webkit-border-radius: 5px;\n    border-radius: 5px;\n    -moz-border-radius: 5px;\n    background-clip: padding-box;\n    margin: 180px auto;\n    width: 350px;\n    padding: 35px 35px 15px 35px;\n    background: #fff;\n    border: 1px solid #eaeaea;\n    box-shadow: 0 0 25px #cac6c6;\n    .title {\n      margin: 0px auto 40px auto;\n      text-align: center;\n      color: #505458;\n    }\n    .remember {\n      margin: 0px 0px 35px 0px;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/general/PruneTestCase.vue",
    "content": "<template>\n  <div>\n    <panel>\n      <span slot=\"title\">{{$t('m.Test_Case_Prune_Test_Case')}}\n        <el-popover placement=\"right\" trigger=\"hover\">\n          These test cases are not owned by any problem, you can clean them safely.\n          <i slot=\"reference\" class=\"el-icon-fa-question-circle import-user-icon\"></i>\n        </el-popover>\n      </span>\n      <el-table :data=\"data\">\n        <el-table-column\n          label=\"Last Modified\">\n          <template slot-scope=\"{row}\">\n            {{row.create_time | timestampFormat }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"id\"\n          label=\"Test Case ID\">\n        </el-table-column>\n        <el-table-column\n          label=\"Option\"\n          fixed=\"right\"\n          width=\"200\">\n          <template slot-scope=\"{row}\">\n            <icon-btn name=\"Delete\" icon=\"trash\" @click.native=\"deleteTestCase(row.id)\"></icon-btn>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"panel-options\" v-show=\"data.length > 0\">\n        <el-button type=\"warning\" size=\"small\"\n                   :loading=\"loading\"\n                   icon=\"el-icon-fa-trash\"\n                   @click=\"deleteTestCase()\">Delete All\n        </el-button>\n      </div>\n    </panel>\n  </div>\n</template>\n\n<script>\n  import api from '@admin/api'\n  import moment from 'moment'\n\n  export default {\n    name: 'prune-test-case',\n    data () {\n      return {\n        data: [],\n        loading: false\n      }\n    },\n    mounted () {\n      this.init()\n    },\n    methods: {\n      init () {\n        api.getInvalidTestCaseList().then(resp => {\n          this.data = resp.data.data\n        }, () => {\n        })\n      },\n      deleteTestCase (id) {\n        if (!id) {\n          this.loading = true\n        }\n        api.pruneTestCase(id).then(resp => {\n          this.loading = false\n          this.init()\n        })\n      }\n    },\n    filters: {\n      timestampFormat (value) {\n        return moment.unix(value).format('YYYY-M-D  HH:mm:ss')\n      }\n    }\n  }\n</script>\n\n<style>\n\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/general/User.vue",
    "content": "<template>\n  <div class=\"view\">\n    <Panel :title=\"$t('m.User_User') \">\n      <div slot=\"header\">\n        <el-row :gutter=\"20\">\n          <el-col :span=\"8\">\n            <el-button v-show=\"selectedUsers.length\"\n                       type=\"warning\" icon=\"el-icon-fa-trash\"\n                       @click=\"deleteUsers(selectedUserIDs)\">Delete\n            </el-button>\n          </el-col>\n          <el-col :span=\"selectedUsers.length ? 16: 24\">\n            <el-input v-model=\"keyword\" prefix-icon=\"el-icon-search\" placeholder=\"Keywords\"></el-input>\n          </el-col>\n        </el-row>\n      </div>\n      <el-table\n        v-loading=\"loadingTable\"\n        element-loading-text=\"loading\"\n        @selection-change=\"handleSelectionChange\"\n        ref=\"table\"\n        :data=\"userList\"\n        style=\"width: 100%\">\n        <el-table-column type=\"selection\" width=\"55\"></el-table-column>\n\n        <el-table-column prop=\"id\" label=\"ID\"></el-table-column>\n\n        <el-table-column prop=\"username\" label=\"Username\"></el-table-column>\n\n        <el-table-column prop=\"create_time\" label=\"Create Time\">\n          <template slot-scope=\"scope\">\n            {{scope.row.create_time | localtime }}\n          </template>\n        </el-table-column>\n\n        <el-table-column prop=\"last_login\" label=\"Last Login\">\n          <template slot-scope=\"scope\">\n            {{scope.row.last_login | localtime }}\n          </template>\n        </el-table-column>\n\n        <el-table-column prop=\"real_name\" label=\"Real Name\"></el-table-column>\n\n        <el-table-column prop=\"email\" label=\"Email\"></el-table-column>\n\n        <el-table-column prop=\"admin_type\" label=\"User Type\">\n          <template slot-scope=\"scope\">\n            {{ scope.row.admin_type }}\n          </template>\n        </el-table-column>\n\n        <el-table-column fixed=\"right\" label=\"Option\" width=\"200\">\n          <template slot-scope=\"{row}\">\n            <icon-btn name=\"Edit\" icon=\"edit\" @click.native=\"openUserDialog(row.id)\"></icon-btn>\n            <icon-btn name=\"Delete\" icon=\"trash\" @click.native=\"deleteUsers([row.id])\"></icon-btn>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"panel-options\">\n        <el-pagination\n          class=\"page\"\n          layout=\"prev, pager, next\"\n          @current-change=\"currentChange\"\n          :page-size=\"pageSize\"\n          :total=\"total\">\n        </el-pagination>\n      </div>\n    </Panel>\n\n    <Panel>\n      <span slot=\"title\">{{$t('m.Import_User')}}\n        <el-popover placement=\"right\" trigger=\"hover\">\n          <p>Only support csv file without headers, check the <a\n            href=\"http://docs.onlinejudge.me/#/onlinejudge/guide/import_users\">link</a> for details</p>\n          <i slot=\"reference\" class=\"el-icon-fa-question-circle import-user-icon\"></i>\n        </el-popover>\n      </span>\n      <el-upload v-if=\"!uploadUsers.length\"\n                 action=\"\"\n                 :show-file-list=\"false\"\n                 accept=\".csv\"\n                 :before-upload=\"handleUsersCSV\">\n        <el-button size=\"small\" icon=\"el-icon-fa-upload\" type=\"primary\">Choose File</el-button>\n      </el-upload>\n      <template v-else>\n        <el-table :data=\"uploadUsersPage\">\n          <el-table-column label=\"Username\">\n            <template slot-scope=\"{row}\">\n              {{row[0]}}\n            </template>\n          </el-table-column>\n          <el-table-column label=\"Password\">\n            <template slot-scope=\"{row}\">\n              {{row[1]}}\n            </template>\n          </el-table-column>\n          <el-table-column label=\"Email\">\n            <template slot-scope=\"{row}\">\n              {{row[2]}}\n            </template>\n          </el-table-column>\n          <el-table-column label=\"RealName\">\n            <template slot-scope=\"{row}\">\n              {{row[3]}}\n            </template>\n          </el-table-column>\n        </el-table>\n        <div class=\"panel-options\">\n          <el-button type=\"primary\" size=\"small\"\n                     icon=\"el-icon-fa-upload\"\n                     @click=\"handleUsersUpload\">Import All\n          </el-button>\n          <el-button type=\"warning\" size=\"small\"\n                     icon=\"el-icon-fa-undo\"\n                     @click=\"handleResetData\">Reset Data\n          </el-button>\n          <el-pagination\n            class=\"page\"\n            layout=\"prev, pager, next\"\n            :page-size=\"uploadUsersPageSize\"\n            :current-page.sync=\"uploadUsersCurrentPage\"\n            :total=\"uploadUsers.length\">\n          </el-pagination>\n        </div>\n      </template>\n    </Panel>\n\n    <Panel :title=\"$t('m.Generate_User')\">\n      <el-form :model=\"formGenerateUser\" ref=\"formGenerateUser\">\n        <el-row type=\"flex\" justify=\"space-between\">\n          <el-col :span=\"4\">\n            <el-form-item label=\"Prefix\" prop=\"prefix\">\n              <el-input v-model=\"formGenerateUser.prefix\" placeholder=\"Prefix\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"4\">\n            <el-form-item label=\"Suffix\" prop=\"suffix\">\n              <el-input v-model=\"formGenerateUser.suffix\" placeholder=\"Suffix\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"4\">\n            <el-form-item label=\"Start Number\" prop=\"number_from\" required>\n              <el-input-number v-model=\"formGenerateUser.number_from\" style=\"width: 100%\"></el-input-number>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"4\">\n            <el-form-item label=\"End Number\" prop=\"number_to\" required>\n              <el-input-number v-model=\"formGenerateUser.number_to\" style=\"width: 100%\"></el-input-number>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"4\">\n            <el-form-item label=\"Password Length\" prop=\"password_length\" required>\n              <el-input v-model=\"formGenerateUser.password_length\"\n                        placeholder=\"Password Length\"></el-input>\n            </el-form-item>\n          </el-col>\n        </el-row>\n\n        <el-form-item>\n          <el-button type=\"primary\" @click=\"generateUser\" icon=\"el-icon-fa-users\" :loading=\"loadingGenerate\">Generate & Export\n          </el-button>\n          <span class=\"userPreview\" v-if=\"formGenerateUser.number_from && formGenerateUser.number_to &&\n                                          formGenerateUser.number_from <= formGenerateUser.number_to\">\n            The usernames will be {{formGenerateUser.prefix + formGenerateUser.number_from + formGenerateUser.suffix}},\n            <span v-if=\"formGenerateUser.number_from + 1 < formGenerateUser.number_to\">\n              {{formGenerateUser.prefix + (formGenerateUser.number_from + 1) + formGenerateUser.suffix + '...'}}\n            </span>\n            <span v-if=\"formGenerateUser.number_from + 1 <= formGenerateUser.number_to\">\n              {{formGenerateUser.prefix + formGenerateUser.number_to + formGenerateUser.suffix}}\n            </span>\n          </span>\n        </el-form-item>\n      </el-form>\n    </Panel>\n    <!--对话框-->\n    <el-dialog :title=\"$t('m.User_Info')\" :visible.sync=\"showUserDialog\" :close-on-click-modal=\"false\">\n      <el-form :model=\"user\" label-width=\"120px\" label-position=\"left\">\n        <el-row :gutter=\"20\">\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.User_Username')\" required>\n              <el-input v-model=\"user.username\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.User_Real_Name')\" required>\n              <el-input v-model=\"user.real_name\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.User_Email')\" required>\n              <el-input v-model=\"user.email\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.User_New_Password')\">\n              <el-input v-model=\"user.password\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.User_Type')\">\n              <el-select v-model=\"user.admin_type\">\n                <el-option label=\"Regular User\" value=\"Regular User\"></el-option>\n                <el-option label=\"Admin\" value=\"Admin\"></el-option>\n                <el-option label=\"Super Admin\" value=\"Super Admin\"></el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"12\">\n            <el-form-item :label=\"$t('m.Problem_Permission')\">\n              <el-select v-model=\"user.problem_permission\" :disabled=\"user.admin_type!=='Admin'\">\n                <el-option label=\"None\" value=\"None\"></el-option>\n                <el-option label=\"Own\" value=\"Own\"></el-option>\n                <el-option label=\"All\" value=\"All\"></el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Two_Factor_Auth')\">\n              <el-switch\n                v-model=\"user.two_factor_auth\"\n                :disabled=\"!user.real_tfa\"\n                active-color=\"#13ce66\"\n                inactive-color=\"#ff4949\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item label=\"Open Api\">\n              <el-switch\n                v-model=\"user.open_api\"\n                active-color=\"#13ce66\"\n                inactive-color=\"#ff4949\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Is_Disabled')\">\n              <el-switch\n                v-model=\"user.is_disabled\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n        </el-row>\n      </el-form>\n      <span slot=\"footer\" class=\"dialog-footer\">\n        <cancel @click.native=\"showUserDialog = false\">Cancel</cancel>\n        <save @click.native=\"saveUser()\"></save>\n      </span>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import papa from 'papaparse'\n  import api from '../../api.js'\n  import utils from '@/utils/utils'\n\n  export default {\n    name: 'User',\n    data () {\n      return {\n        // 一页显示的用户数\n        pageSize: 10,\n        // 用户总数\n        total: 0,\n        // 用户列表\n        userList: [],\n        uploadUsers: [],\n        uploadUsersPage: [],\n        uploadUsersCurrentPage: 1,\n        uploadUsersPageSize: 15,\n        // 搜索关键字\n        keyword: '',\n        // 是否显示用户对话框\n        showUserDialog: false,\n        // 当前用户model\n        user: {},\n        loadingTable: false,\n        loadingGenerate: false,\n        // 当前页码\n        currentPage: 0,\n        selectedUsers: [],\n        formGenerateUser: {\n          prefix: '',\n          suffix: '',\n          number_from: 0,\n          number_to: 0,\n          password_length: 8\n        }\n      }\n    },\n    mounted () {\n      this.getUserList(1)\n    },\n    methods: {\n      // 切换页码回调\n      currentChange (page) {\n        this.currentPage = page\n        this.getUserList(page)\n      },\n      // 提交修改用户的信息\n      saveUser () {\n        api.editUser(this.user).then(res => {\n          // 更新列表\n          this.getUserList(this.currentPage)\n        }).then(() => {\n          this.showUserDialog = false\n        }).catch(() => {\n        })\n      },\n      // 打开用户对话框\n      openUserDialog (id) {\n        this.showUserDialog = true\n        api.getUser(id).then(res => {\n          this.user = res.data.data\n          this.user.password = ''\n          this.user.real_tfa = this.user.two_factor_auth\n        })\n      },\n      // 获取用户列表\n      getUserList (page) {\n        this.loadingTable = true\n        api.getUserList((page - 1) * this.pageSize, this.pageSize, this.keyword).then(res => {\n          this.loadingTable = false\n          this.total = res.data.data.total\n          this.userList = res.data.data.results\n        }, res => {\n          this.loadingTable = false\n        })\n      },\n      deleteUsers (ids) {\n        this.$confirm('Sure to delete the user? The associated resources created by this user will be deleted as well, like problem, contest, announcement, etc.', 'confirm', {\n          type: 'warning'\n        }).then(() => {\n          api.deleteUsers(ids.join(',')).then(res => {\n            this.getUserList(this.currentPage)\n          }).catch(() => {\n            this.getUserList(this.currentPage)\n          })\n        }, () => {\n        })\n      },\n      handleSelectionChange (val) {\n        this.selectedUsers = val\n      },\n      generateUser () {\n        this.$refs['formGenerateUser'].validate((valid) => {\n          if (!valid) {\n            this.$error('Please validate the error fields')\n            return\n          }\n          this.loadingGenerate = true\n          let data = Object.assign({}, this.formGenerateUser)\n          api.generateUser(data).then(res => {\n            this.loadingGenerate = false\n            let url = '/admin/generate_user?file_id=' + res.data.data.file_id\n            utils.downloadFile(url).then(() => {\n              this.$alert('All users created successfully, the users sheets have downloaded to your disk.', 'Notice')\n            })\n            this.getUserList(1)\n          }).catch(() => {\n            this.loadingGenerate = false\n          })\n        })\n      },\n      handleUsersCSV (file) {\n        papa.parse(file, {\n          complete: (results) => {\n            let data = results.data.filter(user => {\n              return user[0] && user[1] && user[2] && user[3]\n            })\n            let delta = results.data.length - data.length\n            if (delta > 0) {\n              this.$warning(delta + ' users have been filtered due to empty value')\n            }\n            this.uploadUsersCurrentPage = 1\n            this.uploadUsers = data\n            this.uploadUsersPage = data.slice(0, this.uploadUsersPageSize)\n          },\n          error: (error) => {\n            this.$error(error)\n          }\n        })\n      },\n      handleUsersUpload () {\n        api.importUsers(this.uploadUsers).then(res => {\n          this.getUserList(1)\n          this.handleResetData()\n        }).catch(() => {\n        })\n      },\n      handleResetData () {\n        this.uploadUsers = []\n      }\n    },\n    computed: {\n      selectedUserIDs () {\n        let ids = []\n        for (let user of this.selectedUsers) {\n          ids.push(user.id)\n        }\n        return ids\n      }\n    },\n    watch: {\n      'keyword' () {\n        this.currentChange(1)\n      },\n      'user.admin_type' () {\n        if (this.user.admin_type === 'Super Admin') {\n          this.user.problem_permission = 'All'\n        } else if (this.user.admin_type === 'Regular User') {\n          this.user.problem_permission = 'None'\n        }\n      },\n      'uploadUsersCurrentPage' (page) {\n        this.uploadUsersPage = this.uploadUsers.slice((page - 1) * this.uploadUsersPageSize, page * this.uploadUsersPageSize)\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .import-user-icon {\n    color: #555555;\n    margin-left: 4px;\n  }\n\n  .userPreview {\n    padding-left: 10px;\n  }\n\n  .notification {\n    p {\n      margin: 0;\n      text-align: left;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/index.js",
    "content": "import Dashboard from './general/Dashboard.vue'\nimport Announcement from './general/Announcement.vue'\nimport User from './general/User.vue'\nimport Conf from './general/Conf.vue'\nimport JudgeServer from './general/JudgeServer.vue'\nimport PruneTestCase from './general/PruneTestCase.vue'\nimport Problem from './problem/Problem.vue'\nimport ProblemList from './problem/ProblemList.vue'\nimport ContestList from './contest/ContestList.vue'\nimport Contest from './contest/Contest.vue'\nimport Login from './general/Login.vue'\nimport Home from './Home.vue'\nimport ProblemImportOrExport from './problem/ImportAndExport.vue'\n\nexport {\n  Announcement, User, Conf, JudgeServer, Problem, ProblemList, Contest,\n  ContestList, Login, Home, PruneTestCase, Dashboard, ProblemImportOrExport\n}\n"
  },
  {
    "path": "src/pages/admin/views/problem/AddPublicProblem.vue",
    "content": "<template>\n  <div>\n    <el-input\n      v-model=\"keyword\"\n      placeholder=\"Keywords\"\n      prefix-icon=\"el-icon-search\">\n    </el-input>\n    <el-table :data=\"problems\" v-loading=\"loading\">\n      <el-table-column\n        label=\"ID\"\n        width=\"100\"\n        prop=\"id\">\n      </el-table-column>\n      <el-table-column\n        label=\"DisplayID\"\n        width=\"200\"\n        prop=\"_id\">\n      </el-table-column>\n      <el-table-column\n        label=\"Title\"\n        prop=\"title\">\n      </el-table-column>\n      <el-table-column\n        label=\"option\"\n        align=\"center\"\n        width=\"100\"\n        fixed=\"right\">\n        <template slot-scope=\"{row}\">\n          <icon-btn icon=\"plus\" name=\"Add the problem\"\n                    @click.native=\"handleAddProblem(row.id)\"></icon-btn>\n        </template>\n      </el-table-column>\n    </el-table>\n\n    <el-pagination\n      class=\"page\"\n      layout=\"prev, pager, next\"\n      @current-change=\"getPublicProblem\"\n      :page-size=\"limit\"\n      :total=\"total\">\n    </el-pagination>\n  </div>\n</template>\n<script>\n  import api from '@admin/api'\n\n  export default {\n    name: 'add-problem-from-public',\n    props: ['contestID'],\n    data () {\n      return {\n        page: 1,\n        limit: 10,\n        total: 0,\n        loading: false,\n        problems: [],\n        contest: {},\n        keyword: ''\n      }\n    },\n    mounted () {\n      api.getContest(this.contestID).then(res => {\n        this.contest = res.data.data\n        this.getPublicProblem()\n      }).catch(() => {\n      })\n    },\n    methods: {\n      getPublicProblem (page) {\n        this.loading = true\n        let params = {\n          keyword: this.keyword,\n          offset: (page - 1) * this.limit,\n          limit: this.limit,\n          rule_type: this.contest.rule_type\n        }\n        api.getProblemList(params).then(res => {\n          this.loading = false\n          this.total = res.data.data.total\n          this.problems = res.data.data.results\n        }).catch(() => {\n        })\n      },\n      handleAddProblem (problemID) {\n        this.$prompt('Please input display id for the contest problem', 'confirm').then(({value}) => {\n          let data = {\n            problem_id: problemID,\n            contest_id: this.contestID,\n            display_id: value\n          }\n          api.addProblemFromPublic(data).then(() => {\n            this.$emit('on-change')\n          }, () => {\n          })\n        }, () => {\n        })\n      }\n    },\n    watch: {\n      'keyword' () {\n        this.getPublicProblem(this.page)\n      }\n    }\n  }\n</script>\n<style scoped>\n  .page {\n    margin-top: 20px;\n    text-align: right\n  }\n\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/problem/ImportAndExport.vue",
    "content": "<template>\n  <div>\n    <div style=\"padding-bottom: 10px;\">\n    </div>\n    <panel title=\"Export Problems (beta)\">\n      <div slot=\"header\">\n        <el-input\n          v-model=\"keyword\"\n          prefix-icon=\"el-icon-search\"\n          placeholder=\"Keywords\">\n        </el-input>\n      </div>\n      <el-table :data=\"problems\"\n                v-loading=\"loadingProblems\" @selection-change=\"handleSelectionChange\">\n        <el-table-column\n          type=\"selection\"\n          width=\"60\">\n        </el-table-column>\n        <el-table-column\n          label=\"ID\"\n          width=\"100\"\n          prop=\"id\">\n        </el-table-column>\n        <el-table-column\n          label=\"DisplayID\"\n          width=\"200\"\n          prop=\"_id\">\n        </el-table-column>\n        <el-table-column\n          label=\"Title\"\n          prop=\"title\">\n        </el-table-column>\n        <el-table-column\n          prop=\"created_by.username\"\n          label=\"Author\">\n        </el-table-column>\n        <el-table-column\n          prop=\"create_time\"\n          label=\"Create Time\">\n          <template slot-scope=\"scope\">\n            {{scope.row.create_time | localtime }}\n          </template>\n        </el-table-column>\n      </el-table>\n\n      <div class=\"panel-options\">\n        <el-button type=\"primary\" size=\"small\" v-show=\"selected_problems.length\"\n                   @click=\"exportProblems\" icon=\"el-icon-fa-arrow-down\">Export\n        </el-button>\n        <el-pagination\n          class=\"page\"\n          layout=\"prev, pager, next\"\n          @current-change=\"getProblems\"\n          :page-size=\"limit\"\n          :total=\"total\">\n        </el-pagination>\n      </div>\n    </panel>\n    <panel title=\"Import QDUOJ Problems (beta)\">\n      <el-upload\n        ref=\"QDU\"\n        action=\"/api/admin/import_problem\"\n        name=\"file\"\n        :file-list=\"fileList1\"\n        :show-file-list=\"true\"\n        :with-credentials=\"true\"\n        :limit=\"3\"\n        :on-change=\"onFile1Change\"\n        :auto-upload=\"false\"\n        :on-success=\"uploadSucceeded\"\n        :on-error=\"uploadFailed\">\n        <el-button size=\"small\" type=\"primary\" icon=\"el-icon-fa-upload\" slot=\"trigger\">Choose File</el-button>\n        <el-button style=\"margin-left: 10px;\" size=\"small\" type=\"success\" @click=\"submitUpload('QDU')\">Upload</el-button>\n      </el-upload>\n    </panel>\n\n    <panel title=\"Import FPS Problems (beta)\">\n      <el-upload\n        ref=\"FPS\"\n        action=\"/api/admin/import_fps\"\n        name=\"file\"\n        :file-list=\"fileList2\"\n        :show-file-list=\"true\"\n        :with-credentials=\"true\"\n        :limit=\"3\"\n        :on-change=\"onFile2Change\"\n        :auto-upload=\"false\"\n        :on-success=\"uploadSucceeded\"\n        :on-error=\"uploadFailed\">\n        <el-button size=\"small\" type=\"primary\" icon=\"el-icon-fa-upload\" slot=\"trigger\">Choose File</el-button>\n        <el-button style=\"margin-left: 10px;\" size=\"small\" type=\"success\" @click=\"submitUpload('FPS')\">Upload</el-button>\n      </el-upload>\n    </panel>\n  </div>\n</template>\n<script>\n  import api from '@admin/api'\n  import utils from '@/utils/utils'\n\n  export default {\n    name: 'import_and_export',\n    data () {\n      return {\n        fileList1: [],\n        fileList2: [],\n        page: 1,\n        limit: 10,\n        total: 0,\n        loadingProblems: false,\n        loadingImporting: false,\n        keyword: '',\n        problems: [],\n        selected_problems: []\n      }\n    },\n    mounted () {\n      this.getProblems()\n    },\n    methods: {\n      handleSelectionChange (val) {\n        this.selected_problems = val\n      },\n      getProblems (page = 1) {\n        let params = {\n          keyword: this.keyword,\n          offset: (page - 1) * this.limit,\n          limit: this.limit\n        }\n        this.loadingProblems = true\n        api.getProblemList(params).then(res => {\n          this.problems = res.data.data.results\n          this.total = res.data.data.total\n          this.loadingProblems = false\n        })\n      },\n      exportProblems () {\n        let params = []\n        for (let p of this.selected_problems) {\n          params.push('problem_id=' + p.id)\n        }\n        let url = '/admin/export_problem?' + params.join('&')\n        utils.downloadFile(url)\n      },\n      submitUpload (ref) {\n        this.$refs[ref].submit()\n      },\n      onFile1Change (file, fileList) {\n        this.fileList1 = fileList.slice(-1)\n      },\n      onFile2Change (file, fileList) {\n        this.fileList2 = fileList.slice(-1)\n      },\n      uploadSucceeded (response) {\n        if (response.error) {\n          this.$error(response.data)\n        } else {\n          this.$success('Successfully imported ' + response.data.import_count + ' problems')\n          this.getProblems()\n        }\n      },\n      uploadFailed () {\n        this.$error('Upload failed')\n      }\n    },\n    watch: {\n      'keyword' () {\n        this.getProblems()\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n\n</style>\n"
  },
  {
    "path": "src/pages/admin/views/problem/Problem.vue",
    "content": "<template>\n  <div class=\"problem\">\n\n    <Panel :title=\"title\">\n      <el-form ref=\"form\" :model=\"problem\" :rules=\"rules\" label-position=\"top\" label-width=\"70px\">\n        <el-row :gutter=\"20\">\n          <el-col :span=\"6\">\n            <el-form-item prop=\"_id\" :label=\"$t('m.Display_ID')\"\n                          :required=\"this.routeName === 'create-contest-problem' || this.routeName === 'edit-contest-problem'\">\n              <el-input :placeholder=\"$t('m.Display_ID')\" v-model=\"problem._id\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"18\">\n            <el-form-item prop=\"title\" :label=\"$t('m.Title')\" required>\n              <el-input :placeholder=\"$t('m.Title')\" v-model=\"problem.title\"></el-input>\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row :gutter=\"20\">\n          <el-col :span=\"24\">\n            <el-form-item prop=\"description\" :label=\"$t('m.Description')\" required>\n              <Simditor v-model=\"problem.description\"></Simditor>\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row :gutter=\"20\">\n          <el-col :span=\"24\">\n            <el-form-item prop=\"input_description\" :label=\"$t('m.Input_Description')\" required>\n              <Simditor v-model=\"problem.input_description\"></Simditor>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"24\">\n            <el-form-item prop=\"output_description\" :label=\"$t('m.Output_Description')\" required>\n              <Simditor v-model=\"problem.output_description\"></Simditor>\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row :gutter=\"20\">\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Time_Limit') + ' (ms)' \" required>\n              <el-input type=\"Number\" :placeholder=\"$t('m.Time_Limit')\" v-model=\"problem.time_limit\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Memory_limit') + ' (MB)' \" required>\n              <el-input type=\"Number\" :placeholder=\"$t('m.Memory_limit')\" v-model=\"problem.memory_limit\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Difficulty')\">\n              <el-select class=\"difficulty-select\" size=\"small\" :placeholder=\"$t('m.Difficulty')\" v-model=\"problem.difficulty\">\n                <el-option :label=\"$t('m.Low')\" value=\"Low\"></el-option>\n                <el-option :label=\"$t('m.Mid')\" value=\"Mid\"></el-option>\n                <el-option :label=\"$t('m.High')\" value=\"High\"></el-option>\n              </el-select>\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row :gutter=\"20\">\n          <el-col :span=\"4\">\n            <el-form-item :label=\"$t('m.Visible')\">\n              <el-switch\n                v-model=\"problem.visible\"\n                active-text=\"\"\n                inactive-text=\"\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"4\">\n            <el-form-item :label=\"$t('m.ShareSubmission')\">\n              <el-switch\n                v-model=\"problem.share_submission\"\n                active-text=\"\"\n                inactive-text=\"\">\n              </el-switch>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Tag')\" :error=\"error.tags\" required>\n              <span class=\"tags\">\n                <el-tag\n                  v-for=\"tag in problem.tags\"\n                  :closable=\"true\"\n                  :close-transition=\"false\"\n                  :key=\"tag\"\n                  type=\"success\"\n                  @close=\"closeTag(tag)\"\n                >{{tag}}</el-tag>\n              </span>\n              <el-autocomplete\n                v-if=\"inputVisible\"\n                size=\"mini\"\n                class=\"input-new-tag\"\n                popper-class=\"problem-tag-poper\"\n                v-model=\"tagInput\"\n                :trigger-on-focus=\"false\"\n                @keyup.enter.native=\"addTag\"\n                @select=\"addTag\"\n                :fetch-suggestions=\"querySearch\">\n              </el-autocomplete>\n              <el-button class=\"button-new-tag\" v-else size=\"small\" @click=\"inputVisible = true\">+ {{$t('m.New_Tag')}}</el-button>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"8\">\n            <el-form-item :label=\"$t('m.Languages')\" :error=\"error.languages\" required>\n              <el-checkbox-group v-model=\"problem.languages\">\n                <el-tooltip class=\"spj-radio\" v-for=\"lang in allLanguage.languages\" :key=\"'spj'+lang.name\" effect=\"dark\"\n                            :content=\"lang.description\" placement=\"top-start\">\n                  <el-checkbox :label=\"lang.name\"></el-checkbox>\n                </el-tooltip>\n              </el-checkbox-group>\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <div>\n          <el-form-item v-for=\"(sample, index) in problem.samples\" :key=\"'sample'+index\">\n            <Accordion :title=\"'Sample' + (index + 1)\">\n              <el-button type=\"warning\" size=\"small\" icon=\"el-icon-delete\" slot=\"header\" @click=\"deleteSample(index)\">\n                Delete\n              </el-button>\n              <el-row :gutter=\"20\">\n                <el-col :span=\"12\">\n                  <el-form-item :label=\"$t('m.Input_Samples')\" required>\n                    <el-input\n                      :rows=\"5\"\n                      type=\"textarea\"\n                      :placeholder=\"$t('m.Input_Samples')\"\n                      v-model=\"sample.input\">\n                    </el-input>\n                  </el-form-item>\n                </el-col>\n                <el-col :span=\"12\">\n                  <el-form-item :label=\"$t('m.Output_Samples')\" required>\n                    <el-input\n                      :rows=\"5\"\n                      type=\"textarea\"\n                      :placeholder=\"$t('m.Output_Samples')\"\n                      v-model=\"sample.output\">\n                    </el-input>\n                  </el-form-item>\n                </el-col>\n              </el-row>\n            </Accordion>\n          </el-form-item>\n        </div>\n        <div class=\"add-sample-btn\">\n          <button type=\"button\" class=\"add-samples\" @click=\"addSample()\"><i class=\"el-icon-plus\"></i>{{$t('m.Add_Sample')}}\n          </button>\n        </div>\n        <el-form-item style=\"margin-top: 20px\" :label=\"$t('m.Hint')\">\n          <Simditor v-model=\"problem.hint\" placeholder=\"\"></Simditor>\n        </el-form-item>\n        <el-form-item :label=\"$t('m.Code_Template')\">\n          <el-row>\n            <el-col :span=\"24\" v-for=\"(v, k) in template\" :key=\"'template'+k\">\n              <el-form-item>\n                <el-checkbox v-model=\"v.checked\">{{ k }}</el-checkbox>\n                <div v-if=\"v.checked\">\n                  <code-mirror v-model=\"v.code\" :mode=\"v.mode\"></code-mirror>\n                </div>\n              </el-form-item>\n            </el-col>\n          </el-row>\n        </el-form-item>\n        <el-form-item :label=\"$t('m.Special_Judge')\" :error=\"error.spj\">\n          <el-col :span=\"24\">\n            <el-checkbox v-model=\"problem.spj\" @click.native.prevent=\"switchSpj()\">{{$t('m.Use_Special_Judge')}}</el-checkbox>\n          </el-col>\n        </el-form-item>\n        <el-form-item v-if=\"problem.spj\">\n          <Accordion :title=\"$t('m.Special_Judge_Code')\">\n            <template slot=\"header\">\n              <span>{{$t('m.SPJ_language')}}</span>\n              <el-radio-group v-model=\"problem.spj_language\">\n                <el-tooltip class=\"spj-radio\" v-for=\"lang in allLanguage.spj_languages\" :key=\"lang.name\" effect=\"dark\"\n                            :content=\"lang.description\" placement=\"top-start\">\n                  <el-radio :label=\"lang.name\">{{ lang.name }}</el-radio>\n                </el-tooltip>\n              </el-radio-group>\n              <el-button type=\"primary\" size=\"small\" icon=\"el-icon-fa-random\" @click=\"compileSPJ\"\n                         :loading=\"loadingCompile\">\n                {{$t('m.Compile')}}\n              </el-button>\n            </template>\n            <code-mirror v-model=\"problem.spj_code\" :mode=\"spjMode\"></code-mirror>\n          </Accordion>\n        </el-form-item>\n        <el-row :gutter=\"20\">\n          <el-col :span=\"4\">\n            <el-form-item :label=\"$t('m.Type')\">\n              <el-radio-group v-model=\"problem.rule_type\" :disabled=\"disableRuleType\">\n                <el-radio label=\"ACM\">ACM</el-radio>\n                <el-radio label=\"OI\">OI</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"6\">\n            <el-form-item :label=\"$t('m.TestCase')\" :error=\"error.testcase\">\n              <el-upload\n                action=\"/api/admin/test_case\"\n                name=\"file\"\n                :data=\"{spj: problem.spj}\"\n                :show-file-list=\"true\"\n                :on-success=\"uploadSucceeded\"\n                :on-error=\"uploadFailed\">\n                <el-button size=\"small\" type=\"primary\" icon=\"el-icon-fa-upload\">Choose File</el-button>\n              </el-upload>\n            </el-form-item>\n          </el-col>\n\n          <el-col :span=\"6\">\n            <el-form-item :label=\"$t('m.IOMode')\">\n              <el-radio-group v-model=\"problem.io_mode.io_mode\">\n                <el-radio label=\"Standard IO\">Standard IO</el-radio>\n                <el-radio label=\"File IO\">File IO</el-radio>\n              </el-radio-group>\n            </el-form-item>\n          </el-col>\n\n          <el-col :span=\"4\" v-if=\"problem.io_mode.io_mode == 'File IO'\">\n            <el-form-item :label=\"$t('m.InputFileName')\" required>\n              <el-input type=\"text\" v-model=\"problem.io_mode.input\"></el-input>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"4\" v-if=\"problem.io_mode.io_mode == 'File IO'\">\n            <el-form-item :label=\"$t('m.OutputFileName')\" required>\n              <el-input type=\"text\" v-model=\"problem.io_mode.output\"></el-input>\n            </el-form-item>\n          </el-col>\n\n          <el-col :span=\"24\">\n            <el-table\n              :data=\"problem.test_case_score\"\n              style=\"width: 100%\">\n              <el-table-column\n                prop=\"input_name\"\n                :label=\"$t('m.Input')\">\n              </el-table-column>\n              <el-table-column\n                prop=\"output_name\"\n                :label=\"$t('m.Output')\">\n              </el-table-column>\n              <el-table-column\n                prop=\"score\"\n                :label=\"$t('m.Score')\">\n                <template slot-scope=\"scope\">\n                  <el-input\n                    size=\"small\"\n                    :placeholder=\"$t('m.Score')\"\n                    v-model=\"scope.row.score\"\n                    :disabled=\"problem.rule_type !== 'OI'\">\n                  </el-input>\n                </template>\n              </el-table-column>\n            </el-table>\n          </el-col>\n        </el-row>\n\n        <el-form-item :label=\"$t('m.Source')\">\n          <el-input :placeholder=\"$t('m.Source')\" v-model=\"problem.source\"></el-input>\n        </el-form-item>\n        <save @click.native=\"submit()\">Save</save>\n      </el-form>\n    </Panel>\n  </div>\n</template>\n\n<script>\n  import Simditor from '../../components/Simditor'\n  import Accordion from '../../components/Accordion'\n  import CodeMirror from '../../components/CodeMirror'\n  import api from '../../api'\n\n  export default {\n    name: 'Problem',\n    components: {\n      Simditor,\n      Accordion,\n      CodeMirror\n    },\n    data () {\n      return {\n        rules: {\n          _id: {required: true, message: 'Display ID is required', trigger: 'blur'},\n          title: {required: true, message: 'Title is required', trigger: 'blur'},\n          input_description: {required: true, message: 'Input Description is required', trigger: 'blur'},\n          output_description: {required: true, message: 'Output Description is required', trigger: 'blur'}\n        },\n        loadingCompile: false,\n        mode: '',\n        contest: {},\n        problem: {\n          languages: [],\n          io_mode: {'io_mode': 'Standard IO', 'input': 'input.txt', 'output': 'output.txt'}\n        },\n        reProblem: {\n          languages: [],\n          io_mode: {'io_mode': 'Standard IO', 'input': 'input.txt', 'output': 'output.txt'}\n        },\n        testCaseUploaded: false,\n        allLanguage: {},\n        inputVisible: false,\n        tagInput: '',\n        template: {},\n        title: '',\n        spjMode: '',\n        disableRuleType: false,\n        routeName: '',\n        error: {\n          tags: '',\n          spj: '',\n          languages: '',\n          testCase: ''\n        }\n      }\n    },\n    mounted () {\n      this.routeName = this.$route.name\n      if (this.routeName === 'edit-problem' || this.routeName === 'edit-contest-problem') {\n        this.mode = 'edit'\n      } else {\n        this.mode = 'add'\n      }\n      api.getLanguages().then(res => {\n        this.problem = this.reProblem = {\n          _id: '',\n          title: '',\n          description: '',\n          input_description: '',\n          output_description: '',\n          time_limit: 1000,\n          memory_limit: 256,\n          difficulty: 'Low',\n          visible: true,\n          share_submission: false,\n          tags: [],\n          languages: [],\n          template: {},\n          samples: [{input: '', output: ''}],\n          spj: false,\n          spj_language: '',\n          spj_code: '',\n          spj_compile_ok: false,\n          test_case_id: '',\n          test_case_score: [],\n          rule_type: 'ACM',\n          hint: '',\n          source: '',\n          io_mode: {'io_mode': 'Standard IO', 'input': 'input.txt', 'output': 'output.txt'}\n        }\n        let contestID = this.$route.params.contestId\n        if (contestID) {\n          this.problem.contest_id = this.reProblem.contest_id = contestID\n          this.disableRuleType = true\n          api.getContest(contestID).then(res => {\n            this.problem.rule_type = this.reProblem.rule_type = res.data.data.rule_type\n            this.contest = res.data.data\n          })\n        }\n\n        this.problem.spj_language = 'C'\n\n        let allLanguage = res.data.data\n        this.allLanguage = allLanguage\n\n        // get problem after getting languages list to avoid find undefined value in `watch problem.languages`\n        if (this.mode === 'edit') {\n          this.title = this.$i18n.t('m.Edit_Problem')\n          let funcName = {'edit-problem': 'getProblem', 'edit-contest-problem': 'getContestProblem'}[this.routeName]\n          api[funcName](this.$route.params.problemId).then(problemRes => {\n            let data = problemRes.data.data\n            if (!data.spj_code) {\n              data.spj_code = ''\n            }\n            data.spj_language = data.spj_language || 'C'\n            this.problem = data\n            this.testCaseUploaded = true\n          })\n        } else {\n          this.title = this.$i18n.t('m.Add_Problem')\n          for (let item of allLanguage.languages) {\n            this.problem.languages.push(item.name)\n          }\n        }\n      })\n    },\n    watch: {\n      '$route' () {\n        this.$refs.form.resetFields()\n        this.problem = this.reProblem\n      },\n      'problem.languages' (newVal) {\n        let data = {}\n        // use deep copy to avoid infinite loop\n        let languages = JSON.parse(JSON.stringify(newVal)).sort()\n        for (let item of languages) {\n          if (this.template[item] === undefined) {\n            let langConfig = this.allLanguage.languages.find(lang => {\n              return lang.name === item\n            })\n            if (this.problem.template[item] === undefined) {\n              data[item] = {checked: false, code: langConfig.config.template, mode: langConfig.content_type}\n            } else {\n              data[item] = {checked: true, code: this.problem.template[item], mode: langConfig.content_type}\n            }\n          } else {\n            data[item] = this.template[item]\n          }\n        }\n        this.template = data\n      },\n      'problem.spj_language' (newVal) {\n        this.spjMode = this.allLanguage.spj_languages.find(item => {\n          return item.name === this.problem.spj_language\n        }).content_type\n      }\n    },\n    methods: {\n      switchSpj () {\n        if (this.testCaseUploaded) {\n          this.$confirm('If you change problem judge method, you need to re-upload test cases', 'Warning', {\n            confirmButtonText: 'Yes',\n            cancelButtonText: 'Cancel',\n            type: 'warning'\n          }).then(() => {\n            this.problem.spj = !this.problem.spj\n            this.resetTestCase()\n          }).catch(() => {\n          })\n        } else {\n          this.problem.spj = !this.problem.spj\n        }\n      },\n      querySearch (queryString, cb) {\n        api.getProblemTagList({ keyword: queryString }).then(res => {\n          let tagList = []\n          for (let tag of res.data.data) {\n            tagList.push({value: tag.name})\n          }\n          cb(tagList)\n        }).catch(() => {\n        })\n      },\n      resetTestCase () {\n        this.testCaseUploaded = false\n        this.problem.test_case_score = []\n        this.problem.test_case_id = ''\n      },\n      addTag () {\n        let inputValue = this.tagInput\n        if (inputValue) {\n          this.problem.tags.push(inputValue)\n        }\n        this.inputVisible = false\n        this.tagInput = ''\n      },\n      closeTag (tag) {\n        this.problem.tags.splice(this.problem.tags.indexOf(tag), 1)\n      },\n      addSample () {\n        this.problem.samples.push({input: '', output: ''})\n      },\n      deleteSample (index) {\n        this.problem.samples.splice(index, 1)\n      },\n      uploadSucceeded (response) {\n        if (response.error) {\n          this.$error(response.data)\n          return\n        }\n        let fileList = response.data.info\n        for (let file of fileList) {\n          file.score = (100 / fileList.length).toFixed(0)\n          if (!file.output_name && this.problem.spj) {\n            file.output_name = '-'\n          }\n        }\n        this.problem.test_case_score = fileList\n        this.testCaseUploaded = true\n        this.problem.test_case_id = response.data.id\n      },\n      uploadFailed () {\n        this.$error('Upload failed')\n      },\n      compileSPJ () {\n        let data = {\n          id: this.problem.id,\n          spj_code: this.problem.spj_code,\n          spj_language: this.problem.spj_language\n        }\n        this.loadingCompile = true\n        api.compileSPJ(data).then(res => {\n          this.loadingCompile = false\n          this.problem.spj_compile_ok = true\n          this.error.spj = ''\n        }, err => {\n          this.loadingCompile = false\n          this.problem.spj_compile_ok = false\n          const h = this.$createElement\n          this.$msgbox({\n            title: 'Compile Error',\n            type: 'error',\n            message: h('pre', err.data.data),\n            showCancelButton: false,\n            closeOnClickModal: false,\n            customClass: 'dialog-compile-error'\n          })\n        })\n      },\n      submit () {\n        if (!this.problem.samples.length) {\n          this.$error('Sample is required')\n          return\n        }\n        for (let sample of this.problem.samples) {\n          if (!sample.input || !sample.output) {\n            this.$error('Sample input and output is required')\n            return\n          }\n        }\n        if (!this.problem.tags.length) {\n          this.error.tags = 'Please add at least one tag'\n          this.$error(this.error.tags)\n          return\n        }\n        if (this.problem.spj) {\n          if (!this.problem.spj_code) {\n            this.error.spj = 'Spj code is required'\n            this.$error(this.error.spj)\n          } else if (!this.problem.spj_compile_ok) {\n            this.error.spj = 'SPJ code has not been successfully compiled'\n          }\n          if (this.error.spj) {\n            this.$error(this.error.spj)\n            return\n          }\n        }\n        if (!this.problem.languages.length) {\n          this.error.languages = 'Please choose at least one language for problem'\n          this.$error(this.error.languages)\n          return\n        }\n        if (!this.testCaseUploaded) {\n          this.error.testCase = 'Test case is not uploaded yet'\n          this.$error(this.error.testCase)\n          return\n        }\n        if (this.problem.rule_type === 'OI') {\n          for (let item of this.problem.test_case_score) {\n            try {\n              if (parseInt(item.score) <= 0) {\n                this.$error('Invalid test case score')\n                return\n              }\n            } catch (e) {\n              this.$error('Test case score must be an integer')\n              return\n            }\n          }\n        }\n        this.problem.languages = this.problem.languages.sort()\n        this.problem.template = {}\n        for (let k in this.template) {\n          if (this.template[k].checked) {\n            this.problem.template[k] = this.template[k].code\n          }\n        }\n        let funcName = {\n          'create-problem': 'createProblem',\n          'edit-problem': 'editProblem',\n          'create-contest-problem': 'createContestProblem',\n          'edit-contest-problem': 'editContestProblem'\n        }[this.routeName]\n        // edit contest problem 时, contest_id会被后来的请求覆盖掉\n        if (funcName === 'editContestProblem') {\n          this.problem.contest_id = this.contest.id\n        }\n        api[funcName](this.problem).then(res => {\n          if (this.routeName === 'create-contest-problem' || this.routeName === 'edit-contest-problem') {\n            this.$router.push({name: 'contest-problem-list', params: {contestId: this.$route.params.contestId}})\n          } else {\n            this.$router.push({name: 'problem-list'})\n          }\n        }).catch(() => {\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .problem {\n    .difficulty-select {\n      width: 120px;\n    }\n    .spj-radio {\n      margin-left: 10px;\n      &:last-child {\n        margin-right: 20px;\n      }\n    }\n    .input-new-tag {\n      width: 78px;\n    }\n    .button-new-tag {\n      height: 24px;\n      line-height: 22px;\n      padding-top: 0;\n      padding-bottom: 0;\n    }\n    .tags {\n      .el-tag {\n        margin-right: 10px;\n      }\n    }\n    .accordion {\n      margin-bottom: 10px;\n    }\n    .add-samples {\n      width: 100%;\n      background-color: #fff;\n      border: 1px dashed #aaa;\n      outline: none;\n      cursor: pointer;\n      color: #666;\n      height: 35px;\n      font-size: 14px;\n      &:hover {\n        background-color: #f9fafc;\n      }\n      i {\n        margin-right: 10px;\n      }\n    }\n    .add-sample-btn {\n      margin-bottom: 10px;\n    }\n\n  }\n</style>\n\n<style>\n  .problem-tag-poper {\n    width: 200px !important;\n  }\n  .dialog-compile-error {\n    width: auto;\n    max-width: 80%;\n    overflow-x: scroll;\n  }\n</style>\n\n"
  },
  {
    "path": "src/pages/admin/views/problem/ProblemList.vue",
    "content": "<template>\n  <div class=\"view\">\n    <Panel :title=\"contestId ? this.$i18n.t('m.Contest_Problem_List') : this.$i18n.t('m.Problem_List')\">\n      <div slot=\"header\">\n        <el-input\n          v-model=\"keyword\"\n          prefix-icon=\"el-icon-search\"\n          placeholder=\"Keywords\">\n        </el-input>\n      </div>\n      <el-table\n        v-loading=\"loading\"\n        element-loading-text=\"loading\"\n        ref=\"table\"\n        :data=\"problemList\"\n        @row-dblclick=\"handleDblclick\"\n        style=\"width: 100%\">\n        <el-table-column\n          width=\"100\"\n          prop=\"id\"\n          label=\"ID\">\n        </el-table-column>\n        <el-table-column\n          width=\"150\"\n          label=\"Display ID\">\n          <template slot-scope=\"{row}\">\n            <span v-show=\"!row.isEditing\">{{row._id}}</span>\n            <el-input v-show=\"row.isEditing\" v-model=\"row._id\"\n                      @keyup.enter.native=\"handleInlineEdit(row)\">\n\n            </el-input>\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"title\"\n          label=\"Title\">\n          <template slot-scope=\"{row}\">\n            <span v-show=\"!row.isEditing\">{{row.title}}</span>\n            <el-input v-show=\"row.isEditing\" v-model=\"row.title\"\n                      @keyup.enter.native=\"handleInlineEdit(row)\">\n            </el-input>\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"created_by.username\"\n          label=\"Author\">\n        </el-table-column>\n        <el-table-column\n          width=\"200\"\n          prop=\"create_time\"\n          label=\"Create Time\">\n          <template slot-scope=\"scope\">\n            {{scope.row.create_time | localtime }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          width=\"100\"\n          prop=\"visible\"\n          label=\"Visible\">\n          <template slot-scope=\"scope\">\n            <el-switch v-model=\"scope.row.visible\"\n                       active-text=\"\"\n                       inactive-text=\"\"\n                       @change=\"updateProblem(scope.row)\">\n            </el-switch>\n          </template>\n        </el-table-column>\n        <el-table-column\n          fixed=\"right\"\n          label=\"Operation\"\n          width=\"250\">\n          <div slot-scope=\"scope\">\n            <icon-btn name=\"Edit\" icon=\"edit\" @click.native=\"goEdit(scope.row.id)\"></icon-btn>\n            <icon-btn v-if=\"contestId\" name=\"Make Public\" icon=\"clone\"\n                      @click.native=\"makeContestProblemPublic(scope.row.id)\"></icon-btn>\n            <icon-btn icon=\"download\" name=\"Download TestCase\"\n                      @click.native=\"downloadTestCase(scope.row.id)\"></icon-btn>\n            <icon-btn icon=\"trash\" name=\"Delete Problem\"\n                      @click.native=\"deleteProblem(scope.row.id)\"></icon-btn>\n          </div>\n        </el-table-column>\n      </el-table>\n      <div class=\"panel-options\">\n        <el-button type=\"primary\" size=\"small\"\n                   @click=\"goCreateProblem\" icon=\"el-icon-plus\">Create\n        </el-button>\n        <el-button v-if=\"contestId\" type=\"primary\"\n                   size=\"small\" icon=\"el-icon-plus\"\n                   @click=\"addProblemDialogVisible = true\">Add From Public Problem\n        </el-button>\n        <el-pagination\n          class=\"page\"\n          layout=\"prev, pager, next\"\n          @current-change=\"currentChange\"\n          :page-size=\"pageSize\"\n          :total=\"total\">\n        </el-pagination>\n      </div>\n    </Panel>\n    <el-dialog title=\"Sure to update the problem? \"\n               width=\"20%\"\n               :visible.sync=\"InlineEditDialogVisible\"\n               @close-on-click-modal=\"false\">\n      <div>\n        <p>DisplayID: {{currentRow._id}}</p>\n        <p>Title: {{currentRow.title}}</p>\n      </div>\n      <span slot=\"footer\">\n        <cancel @click.native=\"InlineEditDialogVisible = false; getProblemList(currentPage)\"></cancel>\n        <save @click.native=\"updateProblem(currentRow)\"></save>\n      </span>\n    </el-dialog>\n    <el-dialog title=\"Add Contest Problem\"\n               v-if=\"contestId\"\n               width=\"80%\"\n               :visible.sync=\"addProblemDialogVisible\"\n               @close-on-click-modal=\"false\">\n      <add-problem-component :contestID=\"contestId\" @on-change=\"getProblemList\"></add-problem-component>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import api from '../../api.js'\n  import utils from '@/utils/utils'\n  import AddProblemComponent from './AddPublicProblem.vue'\n\n  export default {\n    name: 'ProblemList',\n    components: {\n      AddProblemComponent\n    },\n    data () {\n      return {\n        pageSize: 10,\n        total: 0,\n        problemList: [],\n        keyword: '',\n        loading: false,\n        currentPage: 1,\n        routeName: '',\n        contestId: '',\n        // for make public use\n        currentProblemID: '',\n        currentRow: {},\n        InlineEditDialogVisible: false,\n        makePublicDialogVisible: false,\n        addProblemDialogVisible: false\n      }\n    },\n    mounted () {\n      this.routeName = this.$route.name\n      this.contestId = this.$route.params.contestId\n      this.getProblemList(this.currentPage)\n    },\n    methods: {\n      handleDblclick (row) {\n        row.isEditing = true\n      },\n      goEdit (problemId) {\n        if (this.routeName === 'problem-list') {\n          this.$router.push({name: 'edit-problem', params: {problemId}})\n        } else if (this.routeName === 'contest-problem-list') {\n          this.$router.push({name: 'edit-contest-problem', params: {problemId: problemId, contestId: this.contestId}})\n        }\n      },\n      goCreateProblem () {\n        if (this.routeName === 'problem-list') {\n          this.$router.push({name: 'create-problem'})\n        } else if (this.routeName === 'contest-problem-list') {\n          this.$router.push({name: 'create-contest-problem', params: {contestId: this.contestId}})\n        }\n      },\n      // 切换页码回调\n      currentChange (page) {\n        this.currentPage = page\n        this.getProblemList(page)\n      },\n      getProblemList (page = 1) {\n        this.loading = true\n        let funcName = this.routeName === 'problem-list' ? 'getProblemList' : 'getContestProblemList'\n        let params = {\n          limit: this.pageSize,\n          offset: (page - 1) * this.pageSize,\n          keyword: this.keyword,\n          contest_id: this.contestId\n        }\n        api[funcName](params).then(res => {\n          this.loading = false\n          this.total = res.data.data.total\n          for (let problem of res.data.data.results) {\n            problem.isEditing = false\n          }\n          this.problemList = res.data.data.results\n        }, res => {\n          this.loading = false\n        })\n      },\n      deleteProblem (id) {\n        this.$confirm('Sure to delete this problem? The associated submissions will be deleted as well.', 'Delete Problem', {\n          type: 'warning'\n        }).then(() => {\n          let funcName = this.routeName === 'problem-list' ? 'deleteProblem' : 'deleteContestProblem'\n          api[funcName](id).then(() => [\n            this.getProblemList(this.currentPage - 1)\n          ]).catch(() => {\n          })\n        }, () => {\n        })\n      },\n      makeContestProblemPublic (problemID) {\n        this.$prompt('Please input display id for the public problem', 'confirm').then(({value}) => {\n          api.makeContestProblemPublic({id: problemID, display_id: value}).catch()\n        }, () => {\n        })\n      },\n      updateProblem (row) {\n        let data = Object.assign({}, row)\n        let funcName = ''\n        if (this.contestId) {\n          data.contest_id = this.contestId\n          funcName = 'editContestProblem'\n        } else {\n          funcName = 'editProblem'\n        }\n        api[funcName](data).then(res => {\n          this.InlineEditDialogVisible = false\n          this.getProblemList(this.currentPage)\n        }).catch(() => {\n          this.InlineEditDialogVisible = false\n        })\n      },\n      handleInlineEdit (row) {\n        this.currentRow = row\n        this.InlineEditDialogVisible = true\n      },\n      downloadTestCase (problemID) {\n        let url = '/admin/test_case?problem_id=' + problemID\n        utils.downloadFile(url)\n      },\n      getPublicProblem () {\n        api.getProblemList()\n      }\n    },\n    watch: {\n      '$route' (newVal, oldVal) {\n        this.contestId = newVal.params.contestId\n        this.routeName = newVal.name\n        this.getProblemList(this.currentPage)\n      },\n      'keyword' () {\n        this.currentChange()\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n</style>\n"
  },
  {
    "path": "src/pages/oj/App.vue",
    "content": "<template>\n  <div>\n    <NavBar></NavBar>\n    <div class=\"content-app\">\n      <transition name=\"fadeInUp\" mode=\"out-in\">\n        <router-view></router-view>\n      </transition>\n      <div class=\"footer\">\n        <p v-html=\"website.website_footer\"></p>\n        <p>Powered by <a href=\"https://github.com/QingdaoU/OnlineJudge\">OnlineJudge</a>\n          <span v-if=\"version\">&nbsp; Version: {{ version }}</span>\n        </p>\n      </div>\n    </div>\n    <BackTop></BackTop>\n  </div>\n</template>\n\n<script>\n  import { mapActions, mapState } from 'vuex'\n  import NavBar from '@oj/components/NavBar.vue'\n\n  export default {\n    name: 'app',\n    components: {\n      NavBar\n    },\n    data () {\n      return {\n        version: process.env.VERSION\n      }\n    },\n    created () {\n      try {\n        document.body.removeChild(document.getElementById('app-loader'))\n      } catch (e) {\n      }\n    },\n    mounted () {\n      this.getWebsiteConfig()\n    },\n    methods: {\n      ...mapActions(['getWebsiteConfig', 'changeDomTitle'])\n    },\n    computed: {\n      ...mapState(['website'])\n    },\n    watch: {\n      'website' () {\n        this.changeDomTitle()\n      },\n      '$route' () {\n        this.changeDomTitle()\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\">\n\n  * {\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box;\n  }\n\n  a {\n    text-decoration: none;\n    background-color: transparent;\n    &:active, &:hover {\n      outline-width: 0;\n    }\n  }\n\n\n  @media screen and (max-width: 1200px) {\n  .content-app {\n    margin-top: 160px;\n    padding: 0 2%;\n  }\n}\n\n@media screen and (min-width: 1200px) {\n  .content-app {\n    margin-top: 80px;\n    padding: 0 2%;\n  }\n}\n\n  .footer {\n    margin-top: 20px;\n    margin-bottom: 10px;\n    text-align: center;\n    font-size: small;\n  }\n\n  .fadeInUp-enter-active {\n    animation: fadeInUp .8s;\n  }\n\n\n</style>\n"
  },
  {
    "path": "src/pages/oj/api.js",
    "content": "import Vue from 'vue'\nimport store from '@/store'\nimport axios from 'axios'\n\nVue.prototype.$http = axios\naxios.defaults.baseURL = '/api'\naxios.defaults.xsrfHeaderName = 'X-CSRFToken'\naxios.defaults.xsrfCookieName = 'csrftoken'\n\nexport default {\n  getWebsiteConf (params) {\n    return ajax('website', 'get', {\n      params\n    })\n  },\n  getAnnouncementList (offset, limit) {\n    let params = {\n      offset: offset,\n      limit: limit\n    }\n    return ajax('announcement', 'get', {\n      params\n    })\n  },\n  login (data) {\n    return ajax('login', 'post', {\n      data\n    })\n  },\n  checkUsernameOrEmail (username, email) {\n    return ajax('check_username_or_email', 'post', {\n      data: {\n        username,\n        email\n      }\n    })\n  },\n  // 注册\n  register (data) {\n    return ajax('register', 'post', {\n      data\n    })\n  },\n  logout () {\n    return ajax('logout', 'get')\n  },\n  getCaptcha () {\n    return ajax('captcha', 'get')\n  },\n  getUserInfo (username = undefined) {\n    return ajax('profile', 'get', {\n      params: {\n        username\n      }\n    })\n  },\n  updateProfile (profile) {\n    return ajax('profile', 'put', {\n      data: profile\n    })\n  },\n  freshDisplayID (userID) {\n    return ajax('profile/fresh_display_id', 'get', {\n      params: {\n        user_id: userID\n      }\n    })\n  },\n  twoFactorAuth (method, data) {\n    return ajax('two_factor_auth', method, {\n      data\n    })\n  },\n  tfaRequiredCheck (username) {\n    return ajax('tfa_required', 'post', {\n      data: {\n        username\n      }\n    })\n  },\n  getSessions () {\n    return ajax('sessions', 'get')\n  },\n  deleteSession (sessionKey) {\n    return ajax('sessions', 'delete', {\n      params: {\n        session_key: sessionKey\n      }\n    })\n  },\n  applyResetPassword (data) {\n    return ajax('apply_reset_password', 'post', {\n      data\n    })\n  },\n  resetPassword (data) {\n    return ajax('reset_password', 'post', {\n      data\n    })\n  },\n  changePassword (data) {\n    return ajax('change_password', 'post', {\n      data\n    })\n  },\n  changeEmail (data) {\n    return ajax('change_email', 'post', {\n      data\n    })\n  },\n  getLanguages () {\n    return ajax('languages', 'get')\n  },\n  getProblemTagList () {\n    return ajax('problem/tags', 'get')\n  },\n  getProblemList (offset, limit, searchParams) {\n    let params = {\n      paging: true,\n      offset,\n      limit\n    }\n    Object.keys(searchParams).forEach((element) => {\n      if (searchParams[element]) {\n        params[element] = searchParams[element]\n      }\n    })\n    return ajax('problem', 'get', {\n      params: params\n    })\n  },\n  pickone () {\n    return ajax('pickone', 'get')\n  },\n  getProblem (problemID) {\n    return ajax('problem', 'get', {\n      params: {\n        problem_id: problemID\n      }\n    })\n  },\n  getContestList (offset, limit, searchParams) {\n    let params = {\n      offset,\n      limit\n    }\n    if (searchParams !== undefined) {\n      Object.keys(searchParams).forEach((element) => {\n        if (searchParams[element]) {\n          params[element] = searchParams[element]\n        }\n      })\n    }\n    return ajax('contests', 'get', {\n      params\n    })\n  },\n  getContest (id) {\n    return ajax('contest', 'get', {\n      params: {\n        id\n      }\n    })\n  },\n  getContestAccess (contestID) {\n    return ajax('contest/access', 'get', {\n      params: {\n        contest_id: contestID\n      }\n    })\n  },\n  checkContestPassword (contestID, password) {\n    return ajax('contest/password', 'post', {\n      data: {\n        contest_id: contestID,\n        password\n      }\n    })\n  },\n  getContestAnnouncementList (contestId) {\n    return ajax('contest/announcement', 'get', {\n      params: {\n        contest_id: contestId\n      }\n    })\n  },\n  getContestProblemList (contestId) {\n    return ajax('contest/problem', 'get', {\n      params: {\n        contest_id: contestId\n      }\n    })\n  },\n  getContestProblem (problemID, contestID) {\n    return ajax('contest/problem', 'get', {\n      params: {\n        contest_id: contestID,\n        problem_id: problemID\n      }\n    })\n  },\n  submitCode (data) {\n    return ajax('submission', 'post', {\n      data\n    })\n  },\n  getSubmissionList (offset, limit, params) {\n    params.limit = limit\n    params.offset = offset\n    return ajax('submissions', 'get', {\n      params\n    })\n  },\n  getContestSubmissionList (offset, limit, params) {\n    params.limit = limit\n    params.offset = offset\n    return ajax('contest_submissions', 'get', {\n      params\n    })\n  },\n  getSubmission (id) {\n    return ajax('submission', 'get', {\n      params: {\n        id\n      }\n    })\n  },\n  submissionExists (problemID) {\n    return ajax('submission_exists', 'get', {\n      params: {\n        problem_id: problemID\n      }\n    })\n  },\n  submissionRejudge (id) {\n    return ajax('admin/submission/rejudge', 'get', {\n      params: {\n        id\n      }\n    })\n  },\n  updateSubmission (data) {\n    return ajax('submission', 'put', {\n      data\n    })\n  },\n  getUserRank (offset, limit, rule = 'acm') {\n    let params = {\n      offset,\n      limit,\n      rule\n    }\n    return ajax('user_rank', 'get', {\n      params\n    })\n  },\n  getContestRank (params) {\n    return ajax('contest_rank', 'get', {\n      params\n    })\n  },\n  getACMACInfo (params) {\n    return ajax('admin/contest/acm_helper', 'get', {\n      params\n    })\n  },\n  updateACInfoCheckedStatus (data) {\n    return ajax('admin/contest/acm_helper', 'put', {\n      data\n    })\n  }\n}\n\n/**\n * @param url\n * @param method get|post|put|delete...\n * @param params like queryString. if a url is index?a=1&b=2, params = {a: '1', b: '2'}\n * @param data post data, use for method put|post\n * @returns {Promise}\n */\nfunction ajax (url, method, options) {\n  if (options !== undefined) {\n    var {params = {}, data = {}} = options\n  } else {\n    params = data = {}\n  }\n  return new Promise((resolve, reject) => {\n    axios({\n      url,\n      method,\n      params,\n      data\n    }).then(res => {\n      // API正常返回(status=20x), 是否错误通过有无error判断\n      if (res.data.error !== null) {\n        Vue.prototype.$error(res.data.data)\n        reject(res)\n        // 若后端返回为登录，则为session失效，应退出当前登录用户\n        if (res.data.data.startsWith('Please login')) {\n          store.dispatch('changeModalStatus', {'mode': 'login', 'visible': true})\n        }\n      } else {\n        resolve(res)\n        // if (method !== 'get') {\n        //   Vue.prototype.$success('Succeeded')\n        // }\n      }\n    }, res => {\n      // API请求异常，一般为Server error 或 network error\n      reject(res)\n      Vue.prototype.$error(res.data.data)\n    })\n  })\n}\n"
  },
  {
    "path": "src/pages/oj/components/CodeMirror.vue",
    "content": "<template>\n  <div style=\"margin: 0px 0px 15px 0px\">\n    <Row type=\"flex\" justify=\"space-between\" class=\"header\">\n      <Col :span=12>\n      <div>\n        <span>{{$t('m.Language')}}:</span>\n        <Select :value=\"language\" @on-change=\"onLangChange\" class=\"adjust\">\n          <Option v-for=\"item in languages\" :key=\"item\" :value=\"item\">{{item}}\n          </Option>\n        </Select>\n\n        <Tooltip :content=\"this.$i18n.t('m.Reset_to_default_code_definition')\" placement=\"top\" style=\"margin-left: 10px\">\n          <Button icon=\"refresh\" @click=\"onResetClick\"></Button>\n        </Tooltip>\n\n        <Tooltip :content=\"this.$i18n.t('m.Upload_file')\" placement=\"top\" style=\"margin-left: 10px\">\n          <Button icon=\"upload\" @click=\"onUploadFile\"></Button>\n        </Tooltip>\n\n        <input type=\"file\" id=\"file-uploader\" style=\"display: none\" @change=\"onUploadFileDone\">\n\n      </div>\n      </Col>\n      <Col :span=12>\n      <div class=\"fl-right\">\n        <span>{{$t('m.Theme')}}:</span>\n        <Select :value=\"theme\" @on-change=\"onThemeChange\" class=\"adjust\">\n          <Option v-for=\"item in themes\" :key=\"item.label\" :value=\"item.value\">{{item.label}}\n          </Option>\n        </Select>\n      </div>\n      </Col>\n    </Row>\n    <codemirror :value=\"value\" :options=\"options\" @change=\"onEditorCodeChange\" ref=\"myEditor\">\n    </codemirror>\n  </div>\n</template>\n<script>\n  import utils from '@/utils/utils'\n  import { codemirror } from 'vue-codemirror-lite'\n\n  // theme\n  import 'codemirror/theme/monokai.css'\n  import 'codemirror/theme/solarized.css'\n  import 'codemirror/theme/material.css'\n\n  // mode\n  import 'codemirror/mode/clike/clike.js'\n  import 'codemirror/mode/python/python.js'\n  import 'codemirror/mode/go/go.js'\n  import 'codemirror/mode/javascript/javascript.js'\n\n  // active-line.js\n  import 'codemirror/addon/selection/active-line.js'\n\n  // foldGutter\n  import 'codemirror/addon/fold/foldgutter.css'\n  import 'codemirror/addon/fold/foldgutter.js'\n  import 'codemirror/addon/fold/brace-fold.js'\n  import 'codemirror/addon/fold/indent-fold.js'\n\n  export default {\n    name: 'CodeMirror',\n    components: {\n      codemirror\n    },\n    props: {\n      value: {\n        type: String,\n        default: ''\n      },\n      languages: {\n        type: Array,\n        default: () => {\n          return ['C', 'C++', 'Java', 'Python2']\n        }\n      },\n      language: {\n        type: String,\n        default: 'C++'\n      },\n      theme: {\n        type: String,\n        default: 'solarized'\n      }\n    },\n    data () {\n      return {\n        options: {\n          // codemirror options\n          tabSize: 4,\n          mode: 'text/x-csrc',\n          theme: 'solarized',\n          lineNumbers: true,\n          line: true,\n          // 代码折叠\n          foldGutter: true,\n          gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],\n          // 选中文本自动高亮，及高亮方式\n          styleSelectedText: true,\n          lineWrapping: true,\n          highlightSelectionMatches: {showToken: /\\w/, annotateScrollbar: true}\n        },\n        mode: {\n          'C++': 'text/x-csrc'\n        },\n        themes: [\n          {label: this.$i18n.t('m.Monokai'), value: 'monokai'},\n          {label: this.$i18n.t('m.Solarized_Light'), value: 'solarized'},\n          {label: this.$i18n.t('m.Material'), value: 'material'}\n        ]\n      }\n    },\n    mounted () {\n      utils.getLanguages().then(languages => {\n        let mode = {}\n        languages.forEach(lang => {\n          mode[lang.name] = lang.content_type\n        })\n        this.mode = mode\n        this.editor.setOption('mode', this.mode[this.language])\n      })\n      this.editor.focus()\n    },\n    methods: {\n      onEditorCodeChange (newCode) {\n        this.$emit('update:value', newCode)\n      },\n      onLangChange (newVal) {\n        this.editor.setOption('mode', this.mode[newVal])\n        this.$emit('changeLang', newVal)\n      },\n      onThemeChange (newTheme) {\n        this.editor.setOption('theme', newTheme)\n        this.$emit('changeTheme', newTheme)\n      },\n      onResetClick () {\n        this.$emit('resetCode')\n      },\n      onUploadFile () {\n        document.getElementById('file-uploader').click()\n      },\n      onUploadFileDone () {\n        let f = document.getElementById('file-uploader').files[0]\n        let fileReader = new window.FileReader()\n        let self = this\n        fileReader.onload = function (e) {\n          var text = e.target.result\n          self.editor.setValue(text)\n          document.getElementById('file-uploader').value = ''\n        }\n        fileReader.readAsText(f, 'UTF-8')\n      }\n    },\n    computed: {\n      editor () {\n        // get current editor object\n        return this.$refs.myEditor.editor\n      }\n    },\n    watch: {\n      'theme' (newVal, oldVal) {\n        this.editor.setOption('theme', newVal)\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .header {\n    margin: 5px 5px 15px 5px;\n    .adjust {\n      width: 150px;\n      margin-left: 10px;\n    }\n    .fl-right {\n      float: right;\n    }\n  }\n</style>\n\n<style>\n  .CodeMirror {\n    height: auto !important;\n  }\n  .CodeMirror-scroll {\n    min-height: 300px;\n    max-height: 1000px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/components/Highlight.vue",
    "content": "<template>\n  <pre v-highlight=\"code\"><code :class=\"language\" :style=\"styleObject\"></code></pre>\n</template>\n\n<script>\n  export default {\n    name: 'highlight',\n    data () {\n      return {\n        styleObject: {\n          'border-left': '2px solid green'\n        }\n      }\n    },\n    props: {\n      language: {\n        type: String\n      },\n      code: {\n        required: true,\n        type: String\n      },\n      borderColor: {\n        type: String,\n        default: 'green'\n      }\n    },\n    watch: {\n      'borderColor' (newVal, oldVal) {\n        this.styleObject['border-left'] = '2.5px solid ' + newVal\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  pre {\n    padding: 0;\n    display: block;\n    code {\n      padding: 20px;\n      font-size: 1.1em;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/components/NavBar.vue",
    "content": "<template>\n  <div id=\"header\">\n    <Menu theme=\"light\" mode=\"horizontal\" @on-select=\"handleRoute\" :active-name=\"activeMenu\" class=\"oj-menu\">\n      <div class=\"logo\"><span>{{website.website_name}}</span></div>\n      <Menu-item name=\"/\">\n        <Icon type=\"home\"></Icon>\n        {{$t('m.Home')}}\n      </Menu-item>\n      <Menu-item name=\"/problem\">\n        <Icon type=\"ios-keypad\"></Icon>\n        {{$t('m.NavProblems')}}\n      </Menu-item>\n      <Menu-item name=\"/contest\">\n        <Icon type=\"trophy\"></Icon>\n        {{$t('m.Contests')}}\n      </Menu-item>\n      <Menu-item name=\"/status\">\n        <Icon type=\"ios-pulse-strong\"></Icon>\n        {{$t('m.NavStatus')}}\n      </Menu-item>\n      <Submenu name=\"rank\">\n        <template slot=\"title\">\n          <Icon type=\"podium\"></Icon>\n          {{$t('m.Rank')}}\n        </template>\n        <Menu-item name=\"/acm-rank\">\n          {{$t('m.ACM_Rank')}}\n        </Menu-item>\n        <Menu-item name=\"/oi-rank\">\n          {{$t('m.OI_Rank')}}\n        </Menu-item>\n      </Submenu>\n      <Submenu name=\"about\">\n        <template slot=\"title\">\n          <Icon type=\"information-circled\"></Icon>\n          {{$t('m.About')}}\n        </template>\n        <Menu-item name=\"/about\">\n          {{$t('m.Judger')}}\n        </Menu-item>\n        <Menu-item name=\"/FAQ\">\n          {{$t('m.FAQ')}}\n        </Menu-item>\n      </Submenu>\n      <template v-if=\"!isAuthenticated\">\n        <div class=\"btn-menu\">\n          <Button type=\"ghost\"\n                  ref=\"loginBtn\"\n                  shape=\"circle\"\n                  @click=\"handleBtnClick('login')\">{{$t('m.Login')}}\n          </Button>\n          <Button v-if=\"website.allow_register\"\n                  type=\"ghost\"\n                  shape=\"circle\"\n                  @click=\"handleBtnClick('register')\"\n                  style=\"margin-left: 5px;\">{{$t('m.Register')}}\n          </Button>\n        </div>\n      </template>\n      <template v-else>\n        <Dropdown class=\"drop-menu\" @on-click=\"handleRoute\" placement=\"bottom\" trigger=\"click\">\n          <Button type=\"text\" class=\"drop-menu-title\">{{ user.username }}\n            <Icon type=\"arrow-down-b\"></Icon>\n          </Button>\n          <Dropdown-menu slot=\"list\">\n            <Dropdown-item name=\"/user-home\">{{$t('m.MyHome')}}</Dropdown-item>\n            <Dropdown-item name=\"/status?myself=1\">{{$t('m.MySubmissions')}}</Dropdown-item>\n            <Dropdown-item name=\"/setting/profile\">{{$t('m.Settings')}}</Dropdown-item>\n            <Dropdown-item v-if=\"isAdminRole\" name=\"/admin\">{{$t('m.Management')}}</Dropdown-item>\n            <Dropdown-item divided name=\"/logout\">{{$t('m.Logout')}}</Dropdown-item>\n          </Dropdown-menu>\n        </Dropdown>\n      </template>\n    </Menu>\n    <Modal v-model=\"modalVisible\" :width=\"400\">\n      <div slot=\"header\" class=\"modal-title\">{{$t('m.Welcome_to')}} {{website.website_name_shortcut}}</div>\n      <component :is=\"modalStatus.mode\" v-if=\"modalVisible\"></component>\n      <div slot=\"footer\" style=\"display: none\"></div>\n    </Modal>\n  </div>\n</template>\n\n<script>\n  import { mapGetters, mapActions } from 'vuex'\n  import login from '@oj/views/user/Login'\n  import register from '@oj/views/user/Register'\n\n  export default {\n    components: {\n      login,\n      register\n    },\n    mounted () {\n      this.getProfile()\n    },\n    methods: {\n      ...mapActions(['getProfile', 'changeModalStatus']),\n      handleRoute (route) {\n        if (route && route.indexOf('admin') < 0) {\n          this.$router.push(route)\n        } else {\n          window.open('/admin/')\n        }\n      },\n      handleBtnClick (mode) {\n        this.changeModalStatus({\n          visible: true,\n          mode: mode\n        })\n      }\n    },\n    computed: {\n      ...mapGetters(['website', 'modalStatus', 'user', 'isAuthenticated', 'isAdminRole']),\n      // 跟随路由变化\n      activeMenu () {\n        return '/' + this.$route.path.split('/')[1]\n      },\n      modalVisible: {\n        get () {\n          return this.modalStatus.visible\n        },\n        set (value) {\n          this.changeModalStatus({visible: value})\n        }\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  #header {\n    min-width: 300px;\n    position: fixed;\n    top: 0;\n    left: 0;\n    height: auto;\n    width: 100%;\n    z-index: 1000;\n    background-color: #fff;\n    box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.1);\n    .oj-menu {\n      background: #fdfdfd;\n    }\n\n    .logo {\n      margin-left: 2%;\n      margin-right: 2%;\n      font-size: 20px;\n      float: left;\n      line-height: 60px;\n    }\n\n    .drop-menu {\n      float: right;\n      margin-right: 30px;\n      position: absolute;\n      right: 10px;\n      &-title {\n        font-size: 18px;\n      }\n    }\n    .btn-menu {\n      font-size: 16px;\n      float: right;\n      margin-right: 10px;\n    }\n  }\n\n  .modal {\n    &-title {\n      font-size: 18px;\n      font-weight: 600;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/components/Pagination.vue",
    "content": "<template>\n  <div class=\"page\">\n    <Page :total=\"total\"\n          :page-size=\"pageSize\"\n          @on-change=\"onChange\"\n          @on-page-size-change=\"onPageSizeChange\"\n          :show-sizer=\"showSizer\"\n          :page-size-opts=\"[10, 30, 50, 100, 200]\"\n          :current=\"current\"></Page>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'pagination',\n    props: {\n      total: {\n        required: true,\n        type: Number\n      },\n      pageSize: {\n        required: false,\n        type: Number\n      },\n      showSizer: {\n        required: false,\n        type: Boolean,\n        default: false\n      },\n      current: {\n        required: false,\n        type: Number\n      }\n    },\n    methods: {\n      onChange (page) {\n        if (page < 1) {\n          page = 1\n        }\n        this.$emit('update:current', page)\n        this.$emit('on-change', page)\n      },\n      onPageSizeChange (pageSize) {\n        this.$emit('update:pageSize', pageSize)\n        this.$emit('on-page-size-change', pageSize)\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .page {\n    margin: 20px;\n    float: right;\n  }\n</style>\n\n<style lang=\"less\">\n  .ivu-page-options-sizer {\n    min-width: 85px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/components/Panel.vue",
    "content": "<template>\n  <Card :padding=\"padding\" :shadow=\"shadow\" :dis-hover=\"disHover\" :bordered=\"bordered\">\n    <div slot=\"title\" class=\"panel-title\">\n      <slot name=\"title\"></slot>\n    </div>\n    <div slot=\"extra\" class=\"panel-extra\">\n      <slot name=\"extra\"></slot>\n    </div>\n    <div class=\"panel-body\">\n      <slot></slot>\n    </div>\n  </Card>\n</template>\n\n<script>\n  export default {\n    name: 'Panel',\n    props: {\n      shadow: {\n        required: false,\n        type: Boolean,\n        default: false\n      },\n      padding: {\n        required: false,\n        type: Number,\n        default: 0\n      },\n      disHover: {\n        required: false,\n        type: Boolean,\n        default: false\n      },\n      bordered: {\n        required: false,\n        type: Boolean,\n        default: true\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\">\n  @import (reference) '../../../styles/common.less';\n\n  .panel-title {\n    .section-title;\n    padding: 5px 15px;\n  }\n\n  .panel-extra {\n    line-height: 40px;\n    .ivu-input-icon {\n      line-height: 40px;\n    }\n    ul.filter {\n      > li {\n        display: inline-block;\n        padding: 0 10px;\n      }\n    }\n  }\n  .panel-body {\n    word-break: break-all;\n    word-wrap: break-word;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/components/mixins/emitter.js",
    "content": "function broadcast (componentName, eventName, params) {\n  this.$children.forEach(child => {\n    const name = child.$options.name\n\n    if (name === componentName) {\n      child.$emit.apply(child, [eventName].concat(params))\n    } else {\n      // todo 如果 params 是空数组，接收到的会是 undefined\n      broadcast.apply(child, [componentName, eventName].concat([params]))\n    }\n  })\n}\n\nexport default {\n  methods: {\n    dispatch (componentName, eventName, params) {\n      let parent = this.$parent || this.$root\n      let name = parent.$options.name\n\n      while (parent && (!name || name !== componentName)) {\n        parent = parent.$parent\n\n        if (parent) {\n          name = parent.$options.name\n        }\n      }\n      if (parent) {\n        parent.$emit.apply(parent, [eventName].concat(params))\n      }\n    },\n    broadcast (componentName, eventName, params) {\n      broadcast.call(this, componentName, eventName, params)\n    }\n  }\n}\n"
  },
  {
    "path": "src/pages/oj/components/mixins/form.js",
    "content": "import api from '@oj/api'\n\nexport default {\n  data () {\n    return {\n      captchaSrc: ''\n    }\n  },\n  methods: {\n    validateForm (formName) {\n      return new Promise((resolve, reject) => {\n        this.$refs[formName].validate(valid => {\n          if (!valid) {\n            this.$error('please validate the error fields')\n          } else {\n            resolve(valid)\n          }\n        })\n      })\n    },\n    getCaptchaSrc () {\n      api.getCaptcha().then(res => {\n        this.captchaSrc = res.data.data\n      })\n    }\n  }\n}\n"
  },
  {
    "path": "src/pages/oj/components/mixins/index.js",
    "content": "import Emitter from './emitter'\nimport ProblemMixin from './problem'\nimport FormMixin from './form'\n\nexport {Emitter, ProblemMixin, FormMixin}\n"
  },
  {
    "path": "src/pages/oj/components/mixins/problem.js",
    "content": "import utils from '@/utils/utils'\n\nexport default {\n  data () {\n    return {\n      statusColumn: false\n    }\n  },\n  methods: {\n    getACRate (ACCount, TotalCount) {\n      return utils.getACRate(ACCount, TotalCount)\n    },\n    addStatusColumn (tableColumns, dataProblems) {\n      // 已添加过直接返回\n      if (this.statusColumn) return\n      // 只在有做题记录时才添加column\n      let needAdd = dataProblems.some((item, index) => {\n        if (item.my_status !== null && item.my_status !== undefined) {\n          return true\n        }\n      })\n      if (!needAdd) {\n        return\n      }\n      tableColumns.splice(0, 0, {\n        width: 60,\n        title: ' ',\n        render: (h, params) => {\n          let status = params.row.my_status\n          if (status === null || status === undefined) {\n            return undefined\n          }\n          return h('Icon', {\n            props: {\n              type: status === 0 ? 'checkmark-round' : 'minus-round',\n              size: '16'\n            },\n            style: {\n              color: status === 0 ? '#19be6b' : '#ed3f14'\n            }\n          })\n        }\n      })\n      this.statusColumn = true\n    }\n  }\n}\n"
  },
  {
    "path": "src/pages/oj/components/verticalMenu/verticalMenu-item.vue",
    "content": "<template>\n  <li @click.stop=\"handleClick\" :class=\"{disabled: disabled}\">\n    <slot></slot>\n  </li>\n</template>\n\n<script>\n  import Emitter from '../mixins/emitter'\n\n  export default {\n    name: 'VerticalMenu-item',\n    mixins: [Emitter],\n    props: {\n      route: {\n        type: [String, Object]\n      },\n      disabled: {\n        type: Boolean,\n        default: false\n      }\n    },\n    methods: {\n      handleClick () {\n        if (this.route) {\n          this.dispatch('VerticalMenu', 'on-click', this.route)\n        }\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .disabled {\n    /*background-color: #ccc;*/\n    opacity: 1;\n    /*cursor: not-allowed;*/\n    pointer-events: none;\n    color: #ccc;\n    &:hover {\n      border-left: none;\n      color: #ccc;\n      background: #fff;\n    }\n  }\n\n  li {\n    border-bottom: 1px dashed #e9eaec;\n    color: #495060;\n    display: block;\n    text-align: left;\n    padding: 15px 20px;\n    &:hover {\n      background: #f8f8f9;\n      border-left: 2px solid #5cadff;\n      color: #2d8cf0;\n    }\n    & > .ivu-icon {\n      font-size: 16px;\n      margin-right: 8px;\n    }\n    &:last-child {\n      border-bottom: none;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/components/verticalMenu/verticalMenu.vue",
    "content": "<template>\n  <Card :padding=\"0\" dis-hover>\n    <ul>\n      <slot></slot>\n    </ul>\n  </Card>\n</template>\n\n<script>\n  export default {\n    name: 'VerticalMenu'\n  }\n</script>\n\n<style scoped lang=\"less\">\n</style>\n"
  },
  {
    "path": "src/pages/oj/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <title>OnlineJudge</title>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\"/>\n  <meta name=\"renderer\" content=\"webkit\"/>\n  <link rel=\"shortcut icon\" href=\"/public/website/favicon.ico\">\n\n  <link href=\"/static/css/loader.css\" rel=\"stylesheet\">\n  <script>\n    // IE 10 and earlier\n    if (window.navigator.userAgent.indexOf('MSIE ') > 0 &&\n      window.confirm('Your browser is not supported, click \\'OK\\' to update')) {\n      window.location = 'http://outdatedbrowser.com'\n    }\n  </script>\n</head>\n<body>\n<div id=\"app-loader\">\n  <div class=\"square\"></div>\n  <div class=\"square\"></div>\n  <div class=\"square last\"></div>\n  <div class=\"square clear\"></div>\n  <div class=\"square\"></div>\n  <div class=\"square last\"></div>\n  <div class=\"square clear\"></div>\n  <div class=\"square \"></div>\n  <div class=\"square last\"></div>\n</div>\n\n<div id=\"app\"></div>\n<!-- built files will be auto injected -->\n</body>\n</html>\n"
  },
  {
    "path": "src/pages/oj/index.js",
    "content": "import 'babel-polyfill'\nimport Vue from 'vue'\nimport App from './App.vue'\nimport router from './router'\nimport store from '@/store'\nimport i18n from '@/i18n'\nimport VueClipboard from 'vue-clipboard2'\nimport VueAnalytics from 'vue-analytics'\nimport { GOOGLE_ANALYTICS_ID } from '@/utils/constants'\n\nimport iView from 'iview'\nimport 'iview/dist/styles/iview.css'\n\nimport Panel from '@oj/components/Panel.vue'\nimport VerticalMenu from '@oj/components/verticalMenu/verticalMenu.vue'\nimport VerticalMenuItem from '@oj/components/verticalMenu/verticalMenu-item.vue'\nimport '@/styles/index.less'\n\nimport highlight from '@/plugins/highlight'\nimport katex from '@/plugins/katex'\nimport filters from '@/utils/filters.js'\n\nimport ECharts from 'vue-echarts/components/ECharts.vue'\nimport 'echarts/lib/chart/bar'\nimport 'echarts/lib/chart/line'\nimport 'echarts/lib/chart/pie'\nimport 'echarts/lib/component/title'\nimport 'echarts/lib/component/grid'\nimport 'echarts/lib/component/dataZoom'\nimport 'echarts/lib/component/legend'\nimport 'echarts/lib/component/tooltip'\nimport 'echarts/lib/component/toolbox'\nimport 'echarts/lib/component/markPoint'\n\n// register global utility filters.\nObject.keys(filters).forEach(key => {\n  Vue.filter(key, filters[key])\n})\n\nVue.config.productionTip = false\nVue.use(iView, {\n  i18n: (key, value) => i18n.t(key, value)\n})\n\nVue.use(VueClipboard)\nVue.use(highlight)\nVue.use(katex)\nVue.use(VueAnalytics, {\n  id: GOOGLE_ANALYTICS_ID,\n  router\n})\n\nVue.component('ECharts', ECharts)\nVue.component(VerticalMenu.name, VerticalMenu)\nVue.component(VerticalMenuItem.name, VerticalMenuItem)\nVue.component(Panel.name, Panel)\n\n// 注册全局消息提示\nVue.prototype.$Message.config({\n  duration: 2\n})\nVue.prototype.$error = (s) => Vue.prototype.$Message.error(s)\nVue.prototype.$info = (s) => Vue.prototype.$Message.info(s)\nVue.prototype.$success = (s) => Vue.prototype.$Message.success(s)\n\nnew Vue(Vue.util.extend({router, store, i18n}, App)).$mount('#app')\n"
  },
  {
    "path": "src/pages/oj/router/index.js",
    "content": "import Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport routes from './routes'\nimport storage from '@/utils/storage'\nimport {STORAGE_KEY} from '@/utils/constants'\nimport {sync} from 'vuex-router-sync'\nimport {types, default as store} from '../../../store'\n\nVue.use(VueRouter)\n\nconst router = new VueRouter({\n  mode: 'history',\n  scrollBehavior (to, from, savedPosition) {\n    if (savedPosition) {\n      return savedPosition\n    } else {\n      return {x: 0, y: 0}\n    }\n  },\n  routes\n})\n\n// 全局身份确认\nrouter.beforeEach((to, from, next) => {\n  Vue.prototype.$Loading.start()\n  if (to.matched.some(record => record.meta.requiresAuth)) {\n    if (!storage.get(STORAGE_KEY.AUTHED)) {\n      Vue.prototype.$error('Please login first')\n      store.commit(types.CHANGE_MODAL_STATUS, {mode: 'login', visible: true})\n      next({\n        name: 'home'\n      })\n    } else {\n      next()\n    }\n  } else {\n    next()\n  }\n})\n\nrouter.afterEach((to, from, next) => {\n  Vue.prototype.$Loading.finish()\n})\n\nsync(store, router)\n\nexport default router\n"
  },
  {
    "path": "src/pages/oj/router/routes.js",
    "content": "// all routes here.\nimport {\n  About,\n  ACMRank,\n  Announcements,\n  ApplyResetPassword,\n  FAQ,\n  Home,\n  Logout,\n  NotFound,\n  OIRank,\n  Problem,\n  ProblemList,\n  ResetPassword,\n  SubmissionDetails,\n  SubmissionList,\n  UserHome\n} from '../views'\n\nimport * as Contest from '@oj/views/contest'\nimport * as Setting from '@oj/views/setting'\n\nexport default [\n  {\n    name: 'home',\n    path: '/',\n    meta: {title: 'Home'},\n    component: Home\n  },\n  {\n    name: 'logout',\n    path: '/logout',\n    meta: {title: 'Logout'},\n    component: Logout\n  },\n  {\n    name: 'apply-reset-password',\n    path: '/apply-reset-password',\n    meta: {title: 'Apply Reset Password'},\n    component: ApplyResetPassword\n  },\n  {\n    name: 'reset-password',\n    path: '/reset-password/:token',\n    meta: {title: 'Reset Password'},\n    component: ResetPassword\n  },\n  {\n    name: 'problem-list',\n    path: '/problem',\n    meta: {title: 'Problem List'},\n    component: ProblemList\n  },\n  {\n    name: 'problem-details',\n    path: '/problem/:problemID',\n    meta: {title: 'Problem Details'},\n    component: Problem\n  },\n  {\n    name: 'submission-list',\n    path: '/status',\n    meta: {title: 'Submission List'},\n    component: SubmissionList\n  },\n  {\n    name: 'submission-details',\n    path: '/status/:id/',\n    meta: {title: 'Submission Details'},\n    component: SubmissionDetails\n  },\n  {\n    name: 'contest-list',\n    path: '/contest',\n    meta: {title: 'Contest List'},\n    component: Contest.ContestList\n  },\n  {\n    name: 'contest-details',\n    path: '/contest/:contestID/',\n    component: Contest.ContestDetails,\n    meta: {title: 'Contest Details'},\n    children: [\n      {\n        name: 'contest-submission-list',\n        path: 'submissions',\n        component: SubmissionList\n      },\n      {\n        name: 'contest-problem-list',\n        path: 'problems',\n        component: Contest.ContestProblemList\n      },\n      {\n        name: 'contest-problem-details',\n        path: 'problem/:problemID/',\n        component: Problem\n      },\n      {\n        name: 'contest-announcement-list',\n        path: 'announcements',\n        component: Announcements\n      },\n      {\n        name: 'contest-rank',\n        path: 'rank',\n        component: Contest.ContestRank\n      },\n      {\n        name: 'acm-helper',\n        path: 'helper',\n        component: Contest.ACMContestHelper\n      }\n    ]\n  },\n  {\n    name: 'acm-rank',\n    path: '/acm-rank',\n    meta: {title: 'ACM Rankings'},\n    component: ACMRank\n  },\n  {\n    name: 'oi-rank',\n    path: '/oi-rank',\n    meta: {title: 'OI Rankings'},\n    component: OIRank\n  },\n  {\n    name: 'user-home',\n    path: '/user-home',\n    component: UserHome,\n    meta: {requiresAuth: true, title: 'User Home'}\n  },\n  {\n    path: '/setting',\n    component: Setting.Settings,\n    children: [\n      {\n        name: 'default-setting',\n        path: '',\n        meta: {requiresAuth: true, title: 'Default Settings'},\n        component: Setting.ProfileSetting\n      },\n      {\n        name: 'profile-setting',\n        path: 'profile',\n        meta: {requiresAuth: true, title: 'Profile Settings'},\n        component: Setting.ProfileSetting\n      },\n      {\n        name: 'account-setting',\n        path: 'account',\n        meta: {requiresAuth: true, title: 'Account Settings'},\n        component: Setting.AccountSetting\n      },\n      {\n        name: 'security-setting',\n        path: 'security',\n        meta: {requiresAuth: true, title: 'Security Settings'},\n        component: Setting.SecuritySetting\n      }\n    ]\n  },\n  {\n    path: '/about',\n    name: 'about',\n    meta: {title: 'About'},\n    component: About\n  },\n  {\n    path: '/faq',\n    name: 'faq',\n    meta: {title: 'FAQ'},\n    component: FAQ\n  },\n  {\n    path: '*',\n    meta: {title: '404'},\n    component: NotFound\n  }\n]\n"
  },
  {
    "path": "src/pages/oj/views/contest/ContestDetail.vue",
    "content": "<template>\n  <div class=\"flex-container\">\n    <div id=\"contest-main\">\n      <!--children-->\n      <transition name=\"fadeInUp\">\n        <router-view></router-view>\n      </transition>\n      <!--children end-->\n      <div class=\"flex-container\" v-if=\"route_name === 'contest-details'\">\n        <template>\n          <div id=\"contest-desc\">\n            <Panel :padding=\"20\" shadow>\n              <div slot=\"title\">\n                {{contest.title}}\n              </div>\n              <div slot=\"extra\">\n                <Tag type=\"dot\" :color=\"countdownColor\">\n                  <span id=\"countdown\">{{countdown}}</span>\n                </Tag>\n              </div>\n              <div v-html=\"contest.description\" class=\"markdown-body\"></div>\n              <div v-if=\"passwordFormVisible\" class=\"contest-password\">\n                <Input v-model=\"contestPassword\" type=\"password\"\n                       placeholder=\"contest password\" class=\"contest-password-input\"\n                       @on-enter=\"checkPassword\"/>\n                <Button type=\"info\" @click=\"checkPassword\">Enter</Button>\n              </div>\n            </Panel>\n            <Table :columns=\"columns\" :data=\"contest_table\" disabled-hover style=\"margin-bottom: 40px;\"></Table>\n          </div>\n        </template>\n      </div>\n\n    </div>\n    <div v-show=\"showMenu\" id=\"contest-menu\">\n      <VerticalMenu @on-click=\"handleRoute\">\n        <VerticalMenu-item :route=\"{name: 'contest-details', params: {contestID: contestID}}\">\n          <Icon type=\"home\"></Icon>\n          {{$t('m.Overview')}}\n        </VerticalMenu-item>\n\n        <VerticalMenu-item :disabled=\"contestMenuDisabled\"\n                           :route=\"{name: 'contest-announcement-list', params: {contestID: contestID}}\">\n          <Icon type=\"chatbubble-working\"></Icon>\n          {{$t('m.Announcements')}}\n        </VerticalMenu-item>\n\n        <VerticalMenu-item :disabled=\"contestMenuDisabled\"\n                           :route=\"{name: 'contest-problem-list', params: {contestID: contestID}}\">\n          <Icon type=\"ios-photos\"></Icon>\n          {{$t('m.Problems')}}\n        </VerticalMenu-item>\n\n        <VerticalMenu-item v-if=\"OIContestRealTimePermission\"\n                           :disabled=\"contestMenuDisabled\"\n                           :route=\"{name: 'contest-submission-list'}\">\n          <Icon type=\"navicon-round\"></Icon>\n          {{$t('m.Submissions')}}\n        </VerticalMenu-item>\n\n        <VerticalMenu-item v-if=\"OIContestRealTimePermission\"\n                           :disabled=\"contestMenuDisabled\"\n                           :route=\"{name: 'contest-rank', params: {contestID: contestID}}\">\n          <Icon type=\"stats-bars\"></Icon>\n          {{$t('m.Rankings')}}\n        </VerticalMenu-item>\n\n        <VerticalMenu-item v-if=\"showAdminHelper\"\n                           :route=\"{name: 'acm-helper', params: {contestID: contestID}}\">\n          <Icon type=\"ios-paw\"></Icon>\n          {{$t('m.Admin_Helper')}}\n        </VerticalMenu-item>\n      </VerticalMenu>\n    </div>\n  </div>\n</template>\n\n<script>\n  import moment from 'moment'\n  import api from '@oj/api'\n  import { mapState, mapGetters, mapActions } from 'vuex'\n  import { types } from '@/store'\n  import { CONTEST_STATUS_REVERSE, CONTEST_STATUS } from '@/utils/constants'\n  import time from '@/utils/time'\n\n  export default {\n    name: 'ContestDetail',\n    components: {},\n    data () {\n      return {\n        CONTEST_STATUS: CONTEST_STATUS,\n        route_name: '',\n        btnLoading: false,\n        contestID: '',\n        contestPassword: '',\n        columns: [\n          {\n            title: this.$i18n.t('m.StartAt'),\n            render: (h, params) => {\n              return h('span', time.utcToLocal(params.row.start_time))\n            }\n          },\n          {\n            title: this.$i18n.t('m.EndAt'),\n            render: (h, params) => {\n              return h('span', time.utcToLocal(params.row.end_time))\n            }\n          },\n          {\n            title: this.$i18n.t('m.ContestType'),\n            render: (h, params) => {\n              return h('span', this.$i18n.t('m.' + params.row.contest_type ? params.row.contest_type.replace(' ', '_') : ''))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Rule'),\n            render: (h, params) => {\n              return h('span', this.$i18n.t('m.' + params.row.rule_type))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Creator'),\n            render: (h, data) => {\n              return h('span', data.row.created_by.username)\n            }\n          }\n        ]\n      }\n    },\n    mounted () {\n      this.contestID = this.$route.params.contestID\n      this.route_name = this.$route.name\n      this.$store.dispatch('getContest').then(res => {\n        this.changeDomTitle({title: res.data.data.title})\n        let data = res.data.data\n        let endTime = moment(data.end_time)\n        if (endTime.isAfter(moment(data.now))) {\n          this.timer = setInterval(() => {\n            this.$store.commit(types.NOW_ADD_1S)\n          }, 1000)\n        }\n      })\n    },\n    methods: {\n      ...mapActions(['changeDomTitle']),\n      handleRoute (route) {\n        this.$router.push(route)\n      },\n      checkPassword () {\n        if (this.contestPassword === '') {\n          this.$error('Password can\\'t be empty')\n          return\n        }\n        this.btnLoading = true\n        api.checkContestPassword(this.contestID, this.contestPassword).then((res) => {\n          this.$success('Succeeded')\n          this.$store.commit(types.CONTEST_ACCESS, {access: true})\n          this.btnLoading = false\n        }, (res) => {\n          this.btnLoading = false\n        })\n      }\n    },\n    computed: {\n      ...mapState({\n        showMenu: state => state.contest.itemVisible.menu,\n        contest: state => state.contest.contest,\n        contest_table: state => [state.contest.contest],\n        now: state => state.contest.now\n      }),\n      ...mapGetters(\n        ['contestMenuDisabled', 'contestRuleType', 'contestStatus', 'countdown', 'isContestAdmin',\n          'OIContestRealTimePermission', 'passwordFormVisible']\n      ),\n      countdownColor () {\n        if (this.contestStatus) {\n          return CONTEST_STATUS_REVERSE[this.contestStatus].color\n        }\n      },\n      showAdminHelper () {\n        return this.isContestAdmin && this.contestRuleType === 'ACM'\n      }\n    },\n    watch: {\n      '$route' (newVal) {\n        this.route_name = newVal.name\n        this.contestID = newVal.params.contestID\n        this.changeDomTitle({title: this.contest.title})\n      }\n    },\n    beforeDestroy () {\n      clearInterval(this.timer)\n      this.$store.commit(types.CLEAR_CONTEST)\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  pre {\n    display: inline-block;\n  }\n\n  #countdown {\n    font-size: 16px;\n  }\n\n  .flex-container {\n    #contest-main {\n      flex: 1 1;\n      width: 0;\n      #contest-desc {\n        flex: auto;\n      }\n    }\n    #contest-menu {\n      flex: none;\n      width: 210px;\n      margin-left: 20px;\n    }\n    .contest-password {\n      margin-top: 20px;\n      margin-bottom: -10px;\n      &-input {\n        width: 200px;\n        margin-right: 10px;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/contest/ContestList.vue",
    "content": "<template>\n  <Row type=\"flex\">\n    <Col :span=\"24\">\n    <Panel id=\"contest-card\" shadow>\n      <div slot=\"title\">{{query.rule_type === '' ? this.$i18n.t('m.All') : query.rule_type}} {{$t('m.Contests')}}</div>\n      <div slot=\"extra\">\n        <ul class=\"filter\">\n          <li>\n            <Dropdown @on-click=\"onRuleChange\">\n              <span>{{query.rule_type === '' ? this.$i18n.t('m.Rule') : this.$i18n.t('m.' + query.rule_type)}}\n                <Icon type=\"arrow-down-b\"></Icon>\n              </span>\n              <Dropdown-menu slot=\"list\">\n                <Dropdown-item name=\"\">{{$t('m.All')}}</Dropdown-item>\n                <Dropdown-item name=\"OI\">{{$t('m.OI')}}</Dropdown-item>\n                <Dropdown-item name=\"ACM\">{{$t('m.ACM')}}</Dropdown-item>\n              </Dropdown-menu>\n            </Dropdown>\n          </li>\n          <li>\n            <Dropdown @on-click=\"onStatusChange\">\n              <span>{{query.status === '' ? this.$i18n.t('m.Status') : this.$i18n.t('m.' + CONTEST_STATUS_REVERSE[query.status].name.replace(/ /g,\"_\"))}}\n                <Icon type=\"arrow-down-b\"></Icon>\n              </span>\n              <Dropdown-menu slot=\"list\">\n                <Dropdown-item name=\"\">{{$t('m.All')}}</Dropdown-item>\n                <Dropdown-item name=\"0\">{{$t('m.Underway')}}</Dropdown-item>\n                <Dropdown-item name=\"1\">{{$t('m.Not_Started')}}</Dropdown-item>\n                <Dropdown-item name=\"-1\">{{$t('m.Ended')}}</Dropdown-item>\n              </Dropdown-menu>\n            </Dropdown>\n          </li>\n          <li>\n            <Input id=\"keyword\" @on-enter=\"changeRoute\" @on-click=\"changeRoute\" v-model=\"query.keyword\"\n                   icon=\"ios-search-strong\" placeholder=\"Keyword\"/>\n          </li>\n        </ul>\n      </div>\n      <p id=\"no-contest\" v-if=\"contests.length == 0\">{{$t('m.No_contest')}}</p>\n      <ol id=\"contest-list\">\n        <li v-for=\"contest in contests\" :key=\"contest.title\">\n          <Row type=\"flex\" justify=\"space-between\" align=\"middle\">\n            <img class=\"trophy\" src=\"../../../../assets/Cup.png\"/>\n            <Col :span=\"18\" class=\"contest-main\">\n            <p class=\"title\">\n              <a class=\"entry\" @click.stop=\"goContest(contest)\">\n                {{contest.title}}\n              </a>\n              <template v-if=\"contest.contest_type != 'Public'\">\n                <Icon type=\"ios-locked-outline\" size=\"20\"></Icon>\n              </template>\n            </p>\n            <ul class=\"detail\">\n              <li>\n                <Icon type=\"calendar\" color=\"#3091f2\"></Icon>\n                {{contest.start_time | localtime('YYYY-M-D HH:mm') }}\n              </li>\n              <li>\n                <Icon type=\"android-time\" color=\"#3091f2\"></Icon>\n                {{getDuration(contest.start_time, contest.end_time)}}\n              </li>\n              <li>\n                <Button size=\"small\" shape=\"circle\" @click=\"onRuleChange(contest.rule_type)\">\n                  {{contest.rule_type}}\n                </Button>\n              </li>\n            </ul>\n            </Col>\n            <Col :span=\"4\" style=\"text-align: center\">\n            <Tag type=\"dot\" :color=\"CONTEST_STATUS_REVERSE[contest.status].color\">{{$t('m.' + CONTEST_STATUS_REVERSE[contest.status].name.replace(/ /g, \"_\"))}}</Tag>\n            </Col>\n          </Row>\n        </li>\n      </ol>\n    </Panel>\n    <Pagination :total=\"total\" :page-size.sync=\"limit\" @on-change=\"changeRoute\" :current.sync=\"page\" :show-sizer=\"true\" @on-page-size-change=\"changeRoute\"></Pagination>\n    </Col>\n  </Row>\n\n</template>\n\n<script>\n  import api from '@oj/api'\n  import { mapGetters } from 'vuex'\n  import utils from '@/utils/utils'\n  import Pagination from '@/pages/oj/components/Pagination'\n  import time from '@/utils/time'\n  import { CONTEST_STATUS_REVERSE, CONTEST_TYPE } from '@/utils/constants'\n\n  const limit = 10\n\n  export default {\n    name: 'contest-list',\n    components: {\n      Pagination\n    },\n    data () {\n      return {\n        page: 1,\n        query: {\n          status: '',\n          keyword: '',\n          rule_type: ''\n        },\n        limit: limit,\n        total: 0,\n        rows: '',\n        contests: [],\n        CONTEST_STATUS_REVERSE: CONTEST_STATUS_REVERSE,\n//      for password modal use\n        cur_contest_id: ''\n      }\n    },\n    beforeRouteEnter (to, from, next) {\n      api.getContestList(0, limit).then((res) => {\n        next((vm) => {\n          vm.contests = res.data.data.results\n          vm.total = res.data.data.total\n        })\n      }, (res) => {\n        next()\n      })\n    },\n    methods: {\n      init () {\n        let route = this.$route.query\n        this.query.status = route.status || ''\n        this.query.rule_type = route.rule_type || ''\n        this.query.keyword = route.keyword || ''\n        this.page = parseInt(route.page) || 1\n        this.limit = parseInt(route.limit) || 10\n        this.getContestList(this.page)\n      },\n      getContestList (page = 1) {\n        let offset = (page - 1) * this.limit\n        api.getContestList(offset, this.limit, this.query).then((res) => {\n          this.contests = res.data.data.results\n          this.total = res.data.data.total\n        })\n      },\n      changeRoute () {\n        let query = Object.assign({}, this.query)\n        query.page = this.page\n        query.limit = this.limit\n\n        this.$router.push({\n          name: 'contest-list',\n          query: utils.filterEmptyValue(query)\n        })\n      },\n      onRuleChange (rule) {\n        this.query.rule_type = rule\n        this.page = 1\n        this.changeRoute()\n      },\n      onStatusChange (status) {\n        this.query.status = status\n        this.page = 1\n        this.changeRoute()\n      },\n      goContest (contest) {\n        this.cur_contest_id = contest.id\n        if (contest.contest_type !== CONTEST_TYPE.PUBLIC && !this.isAuthenticated) {\n          this.$error(this.$i18n.t('m.Please_login_first'))\n          this.$store.dispatch('changeModalStatus', {visible: true})\n        } else {\n          this.$router.push({name: 'contest-details', params: {contestID: contest.id}})\n        }\n      },\n\n      getDuration (startTime, endTime) {\n        return time.duration(startTime, endTime)\n      }\n    },\n    computed: {\n      ...mapGetters(['isAuthenticated', 'user'])\n    },\n    watch: {\n      '$route' (newVal, oldVal) {\n        if (newVal !== oldVal) {\n          this.init()\n        }\n      }\n    }\n\n  }\n</script>\n<style lang=\"less\" scoped>\n  #contest-card {\n    #keyword {\n      width: 80%;\n      margin-right: 30px;\n    }\n    #no-contest {\n      text-align: center;\n      font-size: 16px;\n      padding: 20px;\n    }\n    #contest-list {\n      > li {\n        padding: 20px;\n        border-bottom: 1px solid rgba(187, 187, 187, 0.5);\n        list-style: none;\n\n        .trophy {\n          height: 40px;\n          margin-left: 10px;\n          margin-right: -20px;\n        }\n        .contest-main {\n          .title {\n            font-size: 18px;\n            a.entry {\n              color: #495060;\n              &:hover {\n                color: #2d8cf0;\n                border-bottom: 1px solid #2d8cf0;\n              }\n            }\n          }\n          li {\n            display: inline-block;\n            padding: 10px 0 0 10px;\n            &:first-child {\n              padding: 10px 0 0 0;\n            }\n          }\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/contest/children/ACMContestRank.vue",
    "content": "<template>\n  <Panel shadow>\n    <div slot=\"title\">{{ contest.title }}</div>\n    <div slot=\"extra\">\n      <screen-full :height=\"18\" :width=\"18\" class=\"screen-full\"></screen-full>\n      <Poptip trigger=\"hover\" placement=\"left-start\">\n        <Icon type=\"android-settings\" size=\"20\"></Icon>\n        <div slot=\"content\" id=\"switches\">\n          <p>\n            <span>{{$t('m.Menu')}}</span>\n            <i-switch v-model=\"showMenu\"></i-switch>\n            <span>{{$t('m.Chart')}}</span>\n            <i-switch v-model=\"showChart\"></i-switch>\n          </p>\n          <p>\n            <span>{{$t('m.Auto_Refresh')}}(10s)</span>\n            <i-switch :disabled=\"refreshDisabled\" @on-change=\"handleAutoRefresh\"></i-switch>\n          </p>\n          <template v-if=\"isContestAdmin\">\n            <p>\n              <span>{{$t('m.RealName')}}</span>\n              <i-switch v-model=\"showRealName\"></i-switch>\n            </p>\n            <p>\n              <span>{{$t('m.Force_Update')}}</span>\n              <i-switch :disabled=\"refreshDisabled\" v-model=\"forceUpdate\"></i-switch>\n            </p>\n          </template>\n          <template>\n            <Button type=\"primary\" size=\"small\" @click=\"downloadRankCSV\">{{$t('m.download_csv')}}</Button>\n          </template>\n        </div>\n      </Poptip>\n    </div>\n    <div v-show=\"showChart\" class=\"echarts\">\n      <ECharts :options=\"options\" ref=\"chart\" auto-resize></ECharts>\n    </div>\n    <Table ref=\"tableRank\" :columns=\"columns\" :data=\"dataRank\" disabled-hover height=\"600\"></Table>\n    <Pagination :total=\"total\"\n                :page-size.sync=\"limit\"\n                :current.sync=\"page\"\n                @on-change=\"getContestRankData\"\n                @on-page-size-change=\"getContestRankData(1)\"\n                show-sizer></Pagination>\n  </Panel>\n</template>\n<script>\n  import moment from 'moment'\n  import { mapActions } from 'vuex'\n\n  import Pagination from '@oj/components/Pagination'\n  import ContestRankMixin from './contestRankMixin'\n  import time from '@/utils/time'\n  import utils from '@/utils/utils'\n\n  export default {\n    name: 'acm-contest-rank',\n    components: {\n      Pagination\n    },\n    mixins: [ContestRankMixin],\n    data () {\n      return {\n        total: 0,\n        page: 1,\n        contestID: '',\n        columns: [\n          {\n            align: 'center',\n            width: 50,\n            fixed: 'left',\n            render: (h, params) => {\n              return h('span', {}, params.index + (this.page - 1) * this.limit + 1)\n            }\n          },\n          {\n            title: this.$i18n.t('m.User_User'),\n            align: 'center',\n            fixed: 'left',\n            width: 150,\n            render: (h, params) => {\n              return h('a', {\n                style: {\n                  display: 'inline-block',\n                  'max-width': '150px'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push(\n                      {\n                        name: 'user-home',\n                        query: {username: params.row.user.username}\n                      })\n                  }\n                }\n              }, params.row.user.username)\n            }\n          },\n          {\n            title: 'AC / ' + this.$i18n.t('m.Total'),\n            align: 'center',\n            width: 100,\n            render: (h, params) => {\n              return h('span', {}, [\n                h('span', {}, params.row.accepted_number + ' / '),\n                h('a', {\n                  on: {\n                    click: () => {\n                      this.$router.push({\n                        name: 'contest-submission-list',\n                        query: {username: params.row.user.username}\n                      })\n                    }\n                  }\n                }, params.row.submission_number)\n              ])\n            }\n          },\n          {\n            title: this.$i18n.t('m.TotalTime'),\n            align: 'center',\n            width: 100,\n            render: (h, params) => {\n              return h('span', this.parseTotalTime(params.row.total_time))\n            }\n          }\n        ],\n        dataRank: [],\n        options: {\n          title: {\n            text: this.$i18n.t('m.Top_10_Teams'),\n            left: 'center'\n          },\n          dataZoom: [\n            {\n              type: 'inside',\n              filterMode: 'none',\n              xAxisIndex: [0],\n              start: 0,\n              end: 100\n            }\n          ],\n          toolbox: {\n            show: true,\n            feature: {\n              saveAsImage: {show: true, title: this.$i18n.t('m.save_as_image')}\n            },\n            right: '5%'\n          },\n          tooltip: {\n            trigger: 'axis',\n            axisPointer: {\n              type: 'cross',\n              axis: 'x'\n            }\n          },\n          legend: {\n            orient: 'vertical',\n            y: 'center',\n            right: 0,\n            data: [],\n            formatter: (value) => {\n              return utils.breakLongWords(value, 16)\n            },\n            textStyle: {\n              fontSize: 12\n            }\n          },\n          grid: {\n            x: 80,\n            x2: 200\n          },\n          xAxis: [{\n            type: 'time',\n            splitLine: false,\n            axisPointer: {\n              show: true,\n              snap: true\n            }\n          }],\n          yAxis: [\n            {\n              type: 'category',\n              boundaryGap: false,\n              data: [0]\n            }],\n          series: []\n        }\n      }\n    },\n    mounted () {\n      this.contestID = this.$route.params.contestID\n      this.getContestRankData(1)\n      if (this.contestProblems.length === 0) {\n        this.getContestProblems().then((res) => {\n          this.addTableColumns(res.data.data)\n          this.addChartCategory(res.data.data)\n        })\n      } else {\n        this.addTableColumns(this.contestProblems)\n        this.addChartCategory(this.contestProblems)\n      }\n    },\n    methods: {\n      ...mapActions(['getContestProblems']),\n      addChartCategory (contestProblems) {\n        let category = []\n        for (let i = 0; i <= contestProblems.length; ++i) {\n          category.push(i)\n        }\n        this.options.yAxis[0].data = category\n      },\n      applyToChart (rankData) {\n        let [users, seriesData] = [[], []]\n        rankData.forEach(rank => {\n          users.push(rank.user.username)\n          let info = rank.submission_info\n          // 提取出已AC题目的时间\n          let timeData = []\n          Object.keys(info).forEach(problemID => {\n            if (info[problemID].is_ac) {\n              timeData.push(info[problemID].ac_time)\n            }\n          })\n          timeData.sort((a, b) => {\n            return a - b\n          })\n\n          let data = []\n          data.push([this.contest.start_time, 0])\n          // index here can be regarded as stacked accepted number count.\n          for (let [index, value] of timeData.entries()) {\n            let realTime = moment(this.contest.start_time).add(value, 'seconds').format()\n            data.push([realTime, index + 1])\n          }\n          seriesData.push({\n            name: rank.user.username,\n            type: 'line',\n            data\n          })\n        })\n        this.options.legend.data = users\n        this.options.series = seriesData\n      },\n      applyToTable (data) {\n        // deepcopy\n        let dataRank = JSON.parse(JSON.stringify(data))\n        // 从submission_info中取出相应的problem_id 放入到父object中,这么做主要是为了适应iview table的data格式\n        // 见https://www.iviewui.com/components/table\n        dataRank.forEach((rank, i) => {\n          let info = rank.submission_info\n          let cellClass = {}\n          Object.keys(info).forEach(problemID => {\n            dataRank[i][problemID] = info[problemID]\n            dataRank[i][problemID].ac_time = time.secondFormat(dataRank[i][problemID].ac_time)\n            let status = info[problemID]\n            if (status.is_first_ac) {\n              cellClass[problemID] = 'first-ac'\n            } else if (status.is_ac) {\n              cellClass[problemID] = 'ac'\n            } else {\n              cellClass[problemID] = 'wa'\n            }\n          })\n          dataRank[i].cellClassName = cellClass\n        })\n        this.dataRank = dataRank\n      },\n      addTableColumns (problems) {\n        // 根据题目添加table column\n        problems.forEach(problem => {\n          this.columns.push({\n            align: 'center',\n            key: problem.id,\n            width: problems.length > 15 ? 80 : null,\n            renderHeader: (h, params) => {\n              return h('a', {\n                'class': {\n                  'emphasis': true\n                },\n                on: {\n                  click: () => {\n                    this.$router.push({\n                      name: 'contest-problem-details',\n                      params: {\n                        contestID: this.contestID,\n                        problemID: problem._id\n                      }\n                    })\n                  }\n                }\n              }, problem._id)\n            },\n            render: (h, params) => {\n              if (params.row[problem.id]) {\n                let status = params.row[problem.id]\n                let acTime, errorNumber\n                if (status.is_ac) {\n                  acTime = h('span', status.ac_time)\n                }\n                if (status.error_number !== 0) {\n                  errorNumber = h('p', '(-' + status.error_number + ')')\n                }\n                return h('div', [acTime, errorNumber])\n              }\n            }\n          })\n        })\n      },\n      parseTotalTime (totalTime) {\n        let m = moment.duration(totalTime, 's')\n        return [Math.floor(m.asHours()), m.minutes(), m.seconds()].join(':')\n      },\n      downloadRankCSV () {\n        utils.downloadFile(`contest_rank?download_csv=1&contest_id=${this.$route.params.contestID}&force_refrash=${this.forceUpdate ? '1' : '0'}`)\n      }\n    }\n  }\n</script>\n<style scoped lang=\"less\">\n  .echarts {\n    margin: 20px auto;\n    height: 400px;\n    width: 98%;\n  }\n\n  .screen-full {\n    margin-right: 8px;\n  }\n\n  #switches {\n    p {\n      margin-top: 5px;\n      &:first-child {\n        margin-top: 0;\n      }\n      span {\n        margin-left: 8px;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/contest/children/ACMHelper.vue",
    "content": "<template>\n  <panel shadow>\n    <div slot=\"title\">{{$t('m.ACM_Helper')}}</div>\n    <div slot=\"extra\">\n      <ul class=\"filter\">\n        <li>\n          {{$t('m.Auto_Refresh')}} (10s)\n          <i-switch style=\"margin-left: 5px;\" @on-change=\"handleAutoRefresh\"></i-switch>\n        </li>\n        <li>\n          <Button type=\"info\" @click=\"getACInfo\">{{$t('m.Refresh')}}</Button>\n        </li>\n      </ul>\n    </div>\n    <Table :data=\"pagedAcInfo\" :columns=\"columns\" :loading=\"loadingTable\" disabled-hover></Table>\n    <pagination :total=\"total\"\n                :page-size.sync=\"limit\"\n                :current.sync=\"page\"\n                @on-change=\"handlePage\"\n                @on-page-size-change=\"handlePage(1)\"\n                show-sizer></pagination>\n  </panel>\n</template>\n<script>\n  import { mapState, mapActions } from 'vuex'\n  import { types } from '../../../../../store'\n  import moment from 'moment'\n  import Pagination from '@oj/components/Pagination.vue'\n  import api from '@oj/api'\n\n  export default {\n    name: 'acm-helper',\n    components: {\n      Pagination\n    },\n    data () {\n      return {\n        page: 1,\n        total: 0,\n        loadingTable: false,\n        columns: [\n          {\n            title: this.$i18n.t('m.AC_Time'),\n            key: 'ac_time'\n          },\n          {\n            title: this.$i18n.t('m.ProblemID'),\n            align: 'center',\n            key: 'problem_display_id'\n          },\n          {\n            title: this.$i18n.t('m.First_Blood'),\n            align: 'center',\n            render: (h, {row}) => {\n              if (row.ac_info.is_first_ac) {\n                return h('Tag', {\n                  props: {\n                    color: 'red'\n                  }\n                }, this.$i18n.t('m.First_Blood'))\n              } else {\n                return h('span', '----')\n              }\n            }\n          },\n          {\n            title: this.$i18n.t('m.Username'),\n            align: 'center',\n            render: (h, {row}) => {\n              return h('a', {\n                style: {\n                  display: 'inline-block',\n                  'max-width': '150px'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push({\n                      name: 'contest-submission-list',\n                      query: {username: row.username}\n                    })\n                  }\n                }\n              }, row.username)\n            }\n          },\n          {\n            title: this.$i18n.t('m.RealName'),\n            align: 'center',\n            render: (h, {row}) => {\n              return h('span', {\n                style: {\n                  display: 'inline-block',\n                  'max-width': '150px'\n                }\n              }, row.real_name)\n            }\n          },\n          {\n            title: this.$i18n.t('m.Status'),\n            align: 'center',\n            render: (h, {row}) => {\n              return h('Tag', {\n                props: {\n                  color: row.checked ? 'green' : 'yellow'\n                }\n              }, row.checked ? this.$i18n.t('m.Checked') : this.$i18n.t('m.Not_Checked'))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Option'),\n            fixed: 'right',\n            align: 'center',\n            width: 100,\n            render: (h, {row}) => {\n              return h('Button', {\n                props: {\n                  type: 'ghost',\n                  size: 'small',\n                  icon: 'checkmark',\n                  disabled: row.checked\n                },\n                on: {\n                  click: () => {\n                    this.updateCheckedStatus(row)\n                  }\n                }\n              }, this.$i18n.t('m.Check_It'))\n            }\n          }\n        ],\n        acInfo: [],\n        pagedAcInfo: [],\n        problemsMap: []\n      }\n    },\n    mounted () {\n      this.contestID = this.$route.params.contestID\n      if (this.contestProblems.length === 0) {\n        this.getContestProblems().then((res) => {\n          this.mapProblemDisplayID()\n          this.getACInfo()\n        })\n      } else {\n        this.mapProblemDisplayID()\n        this.getACInfo()\n      }\n    },\n    methods: {\n      ...mapActions(['getContestProblems']),\n      mapProblemDisplayID () {\n        let problemsMap = {}\n        this.contestProblems.forEach(ele => {\n          problemsMap[ele.id] = ele._id\n        })\n        this.problemsMap = problemsMap\n      },\n      getACInfo (page = 1) {\n        this.loadingTable = true\n        let params = {\n          contest_id: this.$route.params.contestID\n        }\n        api.getACMACInfo(params).then(res => {\n          this.loadingTable = false\n          let data = res.data.data\n          this.total = data.length\n          this.acInfo = data\n          this.handlePage()\n        }).catch(() => {\n          this.loadingTable = false\n        })\n      },\n      updateCheckedStatus (row) {\n        let data = {\n          rank_id: row.id,\n          contest_id: this.contestID,\n          problem_id: row.problem_id,\n          checked: true\n        }\n        api.updateACInfoCheckedStatus(data).then(res => {\n          this.$success('Succeeded')\n          this.getACInfo()\n        }).catch(() => {\n        })\n      },\n      handleAutoRefresh (value) {\n        if (value) {\n          this.refreshFunc = setInterval(() => {\n            this.page = 1\n            this.getACInfo()\n          }, 10000)\n        } else {\n          clearInterval(this.refreshFunc)\n        }\n      },\n      handlePage (page = 1) {\n        if (page !== 1) {\n          this.loadingTable = true\n        }\n        let pageInfo = this.acInfo.slice((this.page - 1) * this.limit, this.page * this.limit)\n        for (let v of pageInfo) {\n          if (v.init) {\n            continue\n          } else {\n            v.init = true\n            v.problem_display_id = this.problemsMap[v.problem_id]\n            v.ac_time = moment(this.contest.start_time).add(v.ac_info.ac_time, 'seconds').local().format('YYYY-M-D  HH:mm:ss')\n          }\n        }\n        this.pagedAcInfo = pageInfo\n        this.loadingTable = false\n      }\n    },\n    computed: {\n      ...mapState({\n        'contest': state => state.contest.contest,\n        'contestProblems': state => state.contest.contestProblems\n      }),\n      limit: {\n        get () {\n          return this.$store.state.contest.rankLimit\n        },\n        set (value) {\n          this.$store.commit(types.CHANGE_CONTEST_RANK_LIMIT, {rankLimit: value})\n        }\n      }\n    },\n    beforeDestroy () {\n      clearInterval(this.refreshFunc)\n    }\n  }\n</script>\n<style lang=\"less\" scoped>\n\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/contest/children/ContestProblemList.vue",
    "content": "<template>\n  <div>\n    <Panel>\n      <div slot=\"title\">{{$t('m.Problems_List')}}</div>\n      <Table v-if=\"contestRuleType == 'ACM' || OIContestRealTimePermission\"\n             :columns=\"ACMTableColumns\"\n             :data=\"problems\"\n             @on-row-click=\"goContestProblem\"\n             :no-data-text=\"$t('m.No_Problems')\"></Table>\n      <Table v-else\n             :data=\"problems\"\n             :columns=\"OITableColumns\"\n             @on-row-click=\"goContestProblem\"\n             no-data-text=\"$t('m.No_Problems')\"></Table>\n    </Panel>\n  </div>\n</template>\n\n<script>\n  import {mapState, mapGetters} from 'vuex'\n  import {ProblemMixin} from '@oj/components/mixins'\n\n  export default {\n    name: 'ContestProblemList',\n    mixins: [ProblemMixin],\n    data () {\n      return {\n        ACMTableColumns: [\n          {\n            title: '#',\n            key: '_id',\n            sortType: 'asc',\n            width: 150\n          },\n          {\n            title: this.$i18n.t('m.Title'),\n            key: 'title'\n          },\n          {\n            title: this.$i18n.t('m.Total'),\n            key: 'submission_number'\n          },\n          {\n            title: this.$i18n.t('m.AC_Rate'),\n            render: (h, params) => {\n              return h('span', this.getACRate(params.row.accepted_number, params.row.submission_number))\n            }\n          }\n        ],\n        OITableColumns: [\n          {\n            title: '#',\n            key: '_id',\n            width: 150\n          },\n          {\n            title: this.$i18n.t('m.Title'),\n            key: 'title'\n          }\n        ]\n      }\n    },\n    mounted () {\n      this.getContestProblems()\n    },\n    methods: {\n      getContestProblems () {\n        this.$store.dispatch('getContestProblems').then(res => {\n          if (this.isAuthenticated) {\n            if (this.contestRuleType === 'ACM') {\n              this.addStatusColumn(this.ACMTableColumns, res.data.data)\n            } else if (this.OIContestRealTimePermission) {\n              this.addStatusColumn(this.ACMTableColumns, res.data.data)\n            }\n          }\n        })\n      },\n      goContestProblem (row) {\n        this.$router.push({\n          name: 'contest-problem-details',\n          params: {\n            contestID: this.$route.params.contestID,\n            problemID: row._id\n          }\n        })\n      }\n    },\n    computed: {\n      ...mapState({\n        problems: state => state.contest.contestProblems\n      }),\n      ...mapGetters(['isAuthenticated', 'contestRuleType', 'OIContestRealTimePermission'])\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/contest/children/ContestRank.vue",
    "content": "<template>\n  <div>\n    <component :is=\"currentView\"></component>\n  </div>\n</template>\n\n<script>\n  import { mapGetters } from 'vuex'\n  import { types } from '../../../../../store'\n  import ACMContestRank from './ACMContestRank.vue'\n  import OIContestRank from './OIContestRank.vue'\n\n  const NullComponent = {\n    name: 'null-component',\n    template: '<div></div>'\n  }\n\n  export default {\n    name: 'contest-rank',\n    components: {\n      ACMContestRank,\n      OIContestRank,\n      NullComponent\n    },\n    computed: {\n      ...mapGetters(['contestRuleType']),\n      currentView () {\n        if (this.contestRuleType === null) {\n          return 'NullComponent'\n        }\n        return this.contestRuleType === 'ACM' ? 'ACMContestRank' : 'OIContestRank'\n      }\n    },\n    beforeRouteLeave (to, from, next) {\n      this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {menu: true})\n      next()\n    }\n  }\n</script>\n<style lang=\"less\" scoped>\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/contest/children/OIContestRank.vue",
    "content": "<template>\n  <Panel shadow>\n    <div slot=\"title\">{{ contest.title }}</div>\n    <div slot=\"extra\">\n      <screen-full :height=\"18\" :width=\"18\" class=\"screen-full\"></screen-full>\n      <Poptip trigger=\"hover\" placement=\"left-start\">\n        <Icon type=\"android-settings\" size=\"20\"></Icon>\n        <div slot=\"content\" id=\"switches\">\n          <p>\n            <span>{{$t('m.Menu')}}</span>\n            <i-switch v-model=\"showMenu\"></i-switch>\n            <span>{{$t('m.Chart')}}</span>\n            <i-switch v-model=\"showChart\"></i-switch>\n          </p>\n          <p>\n            <span>{{$t('m.Auto_Refresh')}}(10s)</span>\n            <i-switch :disabled=\"refreshDisabled\" @on-change=\"handleAutoRefresh\"></i-switch>\n          </p>\n          <p v-if=\"isContestAdmin\">\n            <span>{{$t('m.RealName')}}</span>\n            <i-switch v-model=\"showRealName\"></i-switch>\n          </p>\n          <p>\n            <Button type=\"primary\" size=\"small\" @click=\"downloadRankCSV\">{{$t('m.download_csv')}}</Button>\n          </p>\n        </div>\n      </Poptip>\n    </div>\n    <div v-show=\"showChart\" class=\"echarts\">\n      <ECharts :options=\"options\" ref=\"chart\" auto-resize></ECharts>\n    </div>\n    <Table ref=\"tableRank\" class=\"auto-resize\" :columns=\"columns\" :data=\"dataRank\" disabled-hover></Table>\n    <Pagination :total=\"total\"\n                :page-size.sync=\"limit\"\n                :current.sync=\"page\"\n                @on-change=\"getContestRankData\"\n                @on-page-size-change=\"getContestRankData(1)\"\n                show-sizer></Pagination>\n  </Panel>\n</template>\n<script>\n  import { mapActions } from 'vuex'\n\n  import Pagination from '@oj/components/Pagination'\n  import ContestRankMixin from './contestRankMixin'\n  import utils from '@/utils/utils'\n\n  export default {\n    name: 'acm-contest-rank',\n    components: {\n      Pagination\n    },\n    mixins: [ContestRankMixin],\n    data () {\n      return {\n        total: 0,\n        page: 1,\n        contestID: '',\n        columns: [\n          {\n            align: 'center',\n            width: 60,\n            render: (h, params) => {\n              return h('span', {}, params.index + (this.page - 1) * this.limit + 1)\n            }\n          },\n          {\n            title: this.$i18n.t('m.User_User'),\n            align: 'center',\n            render: (h, params) => {\n              return h('a', {\n                style: {\n                  display: 'inline-block',\n                  'max-width': '150px'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push(\n                      {\n                        name: 'user-home',\n                        query: {username: params.row.user.username}\n                      })\n                  }\n                }\n              }, params.row.user.username)\n            }\n          },\n          {\n            title: this.$i18n.t('m.Total_Score'),\n            align: 'center',\n            render: (h, params) => {\n              return h('a', {\n                on: {\n                  click: () => {\n                    this.$router.push({\n                      name: 'contest-submission-list',\n                      query: {username: params.row.user.username}\n                    })\n                  }\n                }\n              }, params.row.total_score)\n            }\n          }\n        ],\n        dataRank: [],\n        options: {\n          title: {\n            text: this.$i18n.t('m.Top_10_Teams'),\n            left: 'center'\n          },\n          tooltip: {\n            trigger: 'axis'\n          },\n          toolbox: {\n            show: true,\n            feature: {\n              dataView: {show: true, readOnly: true},\n              magicType: {show: true, type: ['line', 'bar']},\n              saveAsImage: {show: true}\n            },\n            right: '10%'\n          },\n          calculable: true,\n          xAxis: [\n            {\n              type: 'category',\n              data: ['root'],\n              boundaryGap: true,\n              axisLabel: {\n                interval: 0,\n                showMinLabel: true,\n                showMaxLabel: true,\n                align: 'center',\n                formatter: (value, index) => {\n                  return utils.breakLongWords(value, 14)\n                }\n              },\n              axisTick: {\n                alignWithLabel: true\n              }\n            }\n          ],\n          yAxis: [\n            {\n              type: 'value'\n            }\n          ],\n          series: [\n            {\n              name: this.$i18n.t('m.Score'),\n              type: 'bar',\n              barMaxWidth: '80',\n              data: [0],\n              markPoint: {\n                data: [\n                  {type: 'max', name: 'max'}\n                ]\n              }\n            }\n          ]\n        }\n      }\n    },\n    mounted () {\n      this.contestID = this.$route.params.contestID\n      this.getContestRankData(1)\n      if (this.contestProblems.length === 0) {\n        this.getContestProblems().then((res) => {\n          this.addTableColumns(res.data.data)\n        })\n      } else {\n        this.addTableColumns(this.contestProblems)\n      }\n    },\n    methods: {\n      ...mapActions(['getContestProblems']),\n      applyToChart (rankData) {\n        let [usernames, scores] = [[], []]\n        rankData.forEach(ele => {\n          usernames.push(ele.user.username)\n          scores.push(ele.total_score)\n        })\n        this.options.xAxis[0].data = usernames\n        this.options.series[0].data = scores\n      },\n      applyToTable (data) {\n        // deepcopy\n        let dataRank = JSON.parse(JSON.stringify(data))\n        // 从submission_info中取出相应的problem_id 放入到父object中,这么做主要是为了适应iview table的data格式\n        // 见https://www.iviewui.com/components/table\n        dataRank.forEach((rank, i) => {\n          let info = rank.submission_info\n          Object.keys(info).forEach(problemID => {\n            dataRank[i][problemID] = info[problemID]\n          })\n        })\n        this.dataRank = dataRank\n      },\n      addTableColumns (problems) {\n        problems.forEach(problem => {\n          this.columns.push({\n            align: 'center',\n            key: problem.id,\n            renderHeader: (h, params) => {\n              return h('a', {\n                'class': {\n                  'emphasis': true\n                },\n                on: {\n                  click: () => {\n                    this.$router.push({\n                      name: 'contest-problem-details',\n                      params: {\n                        contestID: this.contestID,\n                        problemID: problem._id\n                      }\n                    })\n                  }\n                }\n              }, problem._id)\n            },\n            render: (h, params) => {\n              return h('span', params.row[problem.id])\n            }\n          })\n        })\n      },\n      downloadRankCSV () {\n        utils.downloadFile(`contest_rank?download_csv=1&contest_id=${this.$route.params.contestID}&force_refrash=${this.forceUpdate ? '1' : '0'}`)\n      }\n    }\n  }\n</script>\n<style scoped lang=\"less\">\n  .echarts {\n    margin: 20px auto;\n    height: 400px;\n    width: 98%;\n  }\n\n  .screen-full {\n    margin-right: 8px;\n  }\n\n  #switches {\n    p {\n      margin-top: 5px;\n      &:first-child {\n        margin-top: 0;\n      }\n      span {\n        margin-left: 8px;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/contest/children/contestRankMixin.js",
    "content": "import api from '@oj/api'\nimport ScreenFull from '@admin/components/ScreenFull.vue'\nimport { mapGetters, mapState } from 'vuex'\nimport { types } from '@/store'\nimport { CONTEST_STATUS } from '@/utils/constants'\n\nexport default {\n  components: {\n    ScreenFull\n  },\n  methods: {\n    getContestRankData (page = 1, refresh = false) {\n      let offset = (page - 1) * this.limit\n      if (this.showChart && !refresh) {\n        this.$refs.chart.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'})\n      }\n      let params = {\n        offset,\n        limit: this.limit,\n        contest_id: this.$route.params.contestID,\n        force_refresh: this.forceUpdate ? '1' : '0'\n      }\n      api.getContestRank(params).then(res => {\n        if (this.showChart && !refresh) {\n          this.$refs.chart.hideLoading()\n        }\n        this.total = res.data.data.total\n        if (page === 1) {\n          this.applyToChart(res.data.data.results.slice(0, 10))\n        }\n        this.applyToTable(res.data.data.results)\n      })\n    },\n    handleAutoRefresh (status) {\n      if (status === true) {\n        this.refreshFunc = setInterval(() => {\n          this.page = 1\n          this.getContestRankData(1, true)\n        }, 10000)\n      } else {\n        clearInterval(this.refreshFunc)\n      }\n    }\n  },\n  computed: {\n    ...mapGetters(['isContestAdmin']),\n    ...mapState({\n      'contest': state => state.contest.contest,\n      'contestProblems': state => state.contest.contestProblems\n    }),\n    showChart: {\n      get () {\n        return this.$store.state.contest.itemVisible.chart\n      },\n      set (value) {\n        this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {chart: value})\n      }\n    },\n    showMenu: {\n      get () {\n        return this.$store.state.contest.itemVisible.menu\n      },\n      set (value) {\n        this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {menu: value})\n        this.$nextTick(() => {\n          if (this.showChart) {\n            this.$refs.chart.resize()\n          }\n          this.$refs.tableRank.handleResize()\n        })\n      }\n    },\n    showRealName: {\n      get () {\n        return this.$store.state.contest.itemVisible.realName\n      },\n      set (value) {\n        this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {realName: value})\n        if (value) {\n          this.columns.splice(2, 0, {\n            title: 'RealName',\n            align: 'center',\n            width: 150,\n            render: (h, {row}) => {\n              return h('span', row.user.real_name)\n            }\n          })\n        } else {\n          this.columns.splice(2, 1)\n        }\n      }\n    },\n    forceUpdate: {\n      get () {\n        return this.$store.state.contest.forceUpdate\n      },\n      set (value) {\n        this.$store.commit(types.CHANGE_RANK_FORCE_UPDATE, {value: value})\n      }\n    },\n    limit: {\n      get () {\n        return this.$store.state.contest.rankLimit\n      },\n      set (value) {\n        this.$store.commit(types.CHANGE_CONTEST_RANK_LIMIT, {rankLimit: value})\n      }\n    },\n    refreshDisabled () {\n      return this.contest.status === CONTEST_STATUS.ENDED\n    }\n  },\n  beforeDestroy () {\n    clearInterval(this.refreshFunc)\n  }\n}\n"
  },
  {
    "path": "src/pages/oj/views/contest/index.js",
    "content": "const ContestList = () => import(/* webpackChunkName: \"contest\" */ './ContestList.vue')\nconst ContestDetails = () => import(/* webpackChunkName: \"contest\" */ './ContestDetail.vue')\nconst ContestProblemList = () => import(/* webpackChunkName: \"contest\" */ './children/ContestProblemList.vue')\nconst ContestRank = () => import(/* webpackChunkName: \"contest\" */ './children/ContestRank.vue')\nconst ACMContestHelper = () => import(/* webpackChunkName: \"contest\" */ './children/ACMHelper.vue')\n\nexport {ContestDetails, ContestList, ContestProblemList, ContestRank, ACMContestHelper}\n"
  },
  {
    "path": "src/pages/oj/views/general/404.vue",
    "content": "<template>\n  <div class=\"error404\">\n    <div class=\"error404-body-con\">\n      <Card>\n        <div class=\"error404-body-con-title\">4<span><Icon type=\"ios-navigate-outline\"></Icon></span>4</div>\n        <p class=\"error404-body-con-message\">YOU&nbsp;&nbsp;LOOK&nbsp;&nbsp;LOST</p>\n        <div class=\"error404-btn-con\">\n          <Button @click=\"goHome\" size=\"large\" style=\"width: 200px;\" type=\"ghost\">{{$t('m.Go_Home')}}</Button>\n          <Button @click=\"backPage\" size=\"large\" style=\"width: 200px;margin-left: 40px;\" type=\"primary\">{{$t('m.Back')}}</Button>\n        </div>\n      </Card>\n    </div>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: 'Error404',\n    methods: {\n      backPage () {\n        this.$router.go(-1)\n      },\n      goHome () {\n        this.$router.push({\n          name: 'home'\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  @keyframes error404animation {\n    0% {\n      transform: rotateZ(0deg);\n    }\n    20% {\n      transform: rotateZ(-60deg);\n    }\n    40% {\n      transform: rotateZ(-10deg);\n    }\n    60% {\n      transform: rotateZ(50deg);\n    }\n    80% {\n      transform: rotateZ(-20deg);\n    }\n    100% {\n      transform: rotateZ(0deg);\n    }\n  }\n\n  .error404 {\n    &-body-con {\n      width: 700px;\n      height: 500px;\n      margin: 0 auto;\n      &-title {\n        text-align: center;\n        font-size: 240px;\n        font-weight: 700;\n        color: #2d8cf0;\n        height: 260px;\n        line-height: 260px;\n        margin-top: 40px;\n        span {\n          display: inline-block;\n          color: #19be6b;\n          font-size: 230px;\n          animation: error404animation 3s ease 0s infinite alternate;\n        }\n      }\n      &-message {\n        display: block;\n        text-align: center;\n        font-size: 30px;\n        font-weight: 500;\n        letter-spacing: 12px;\n        color: #dddde2;\n      }\n    }\n    &-btn-con {\n      text-align: center;\n      padding: 20px 0;\n      margin-bottom: 40px;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/general/Announcements.vue",
    "content": "<template>\n  <Panel shadow :padding=\"10\">\n    <div slot=\"title\">\n      {{title}}\n    </div>\n    <div slot=\"extra\">\n      <Button v-if=\"listVisible\" type=\"info\" @click=\"init\" :loading=\"btnLoading\">{{$t('m.Refresh')}}</Button>\n      <Button v-else type=\"ghost\" icon=\"ios-undo\" @click=\"goBack\">{{$t('m.Back')}}</Button>\n    </div>\n\n    <transition-group name=\"announcement-animate\" mode=\"in-out\">\n      <div class=\"no-announcement\" v-if=\"!announcements.length\" key=\"no-announcement\">\n        <p>{{$t('m.No_Announcements')}}</p>\n      </div>\n      <template v-if=\"listVisible\">\n        <ul class=\"announcements-container\" key=\"list\">\n          <li v-for=\"announcement in announcements\" :key=\"announcement.title\">\n            <div class=\"flex-container\">\n              <div class=\"title\"><a class=\"entry\" @click=\"goAnnouncement(announcement)\">\n                {{announcement.title}}</a></div>\n              <div class=\"date\">{{announcement.create_time | localtime }}</div>\n              <div class=\"creator\"> {{$t('m.By')}} {{announcement.created_by.username}}</div>\n            </div>\n          </li>\n        </ul>\n        <Pagination v-if=\"!isContest\"\n                    key=\"page\"\n                    :total=\"total\"\n                    :page-size=\"limit\"\n                    @on-change=\"getAnnouncementList\">\n        </Pagination>\n      </template>\n\n      <template v-else>\n        <div v-katex v-html=\"announcement.content\" key=\"content\" class=\"content-container markdown-body\"></div>\n      </template>\n    </transition-group>\n  </Panel>\n</template>\n\n<script>\n  import api from '@oj/api'\n  import Pagination from '@oj/components/Pagination'\n\n  export default {\n    name: 'Announcement',\n    components: {\n      Pagination\n    },\n    data () {\n      return {\n        limit: 10,\n        total: 10,\n        btnLoading: false,\n        announcements: [],\n        announcement: '',\n        listVisible: true\n      }\n    },\n    mounted () {\n      this.init()\n    },\n    methods: {\n      init () {\n        if (this.isContest) {\n          this.getContestAnnouncementList()\n        } else {\n          this.getAnnouncementList()\n        }\n      },\n      getAnnouncementList (page = 1) {\n        this.btnLoading = true\n        api.getAnnouncementList((page - 1) * this.limit, this.limit).then(res => {\n          this.btnLoading = false\n          this.announcements = res.data.data.results\n          this.total = res.data.data.total\n        }, () => {\n          this.btnLoading = false\n        })\n      },\n      getContestAnnouncementList () {\n        this.btnLoading = true\n        api.getContestAnnouncementList(this.$route.params.contestID).then(res => {\n          this.btnLoading = false\n          this.announcements = res.data.data\n        }, () => {\n          this.btnLoading = false\n        })\n      },\n      goAnnouncement (announcement) {\n        this.announcement = announcement\n        this.listVisible = false\n      },\n      goBack () {\n        this.listVisible = true\n        this.announcement = ''\n      }\n    },\n    computed: {\n      title () {\n        if (this.listVisible) {\n          return this.isContest ? this.$i18n.t('m.Contest_Announcements') : this.$i18n.t('m.Announcements')\n        } else {\n          return this.announcement.title\n        }\n      },\n      isContest () {\n        return !!this.$route.params.contestID\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .announcements-container {\n    margin-top: -10px;\n    margin-bottom: 10px;\n    li {\n      padding-top: 15px;\n      list-style: none;\n      padding-bottom: 15px;\n      margin-left: 20px;\n      font-size: 16px;\n      border-bottom: 1px solid rgba(187, 187, 187, 0.5);\n      &:last-child {\n        border-bottom: none;\n      }\n      .flex-container {\n        .title {\n          flex: 1 1;\n          text-align: left;\n          padding-left: 10px;\n          a.entry {\n            color: #495060;\n            &:hover {\n              color: #2d8cf0;\n              border-bottom: 1px solid #2d8cf0;\n            }\n          }\n        }\n        .creator {\n          flex: none;\n          width: 200px;\n          text-align: center;\n        }\n        .date {\n          flex: none;\n          width: 200px;\n          text-align: center;\n        }\n      }\n    }\n  }\n\n  .content-container {\n    padding: 0 20px 20px 20px;\n  }\n\n  .no-announcement {\n    text-align: center;\n    font-size: 16px;\n  }changeLocale\n\n  .announcement-animate-enter-active {\n    animation: fadeIn 1s;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/general/Home.vue",
    "content": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"22\">\n    <panel shadow v-if=\"contests.length\" class=\"contest\">\n      <div slot=\"title\">\n        <Button type=\"text\"  class=\"contest-title\" @click=\"goContest\">{{contests[index].title}}</Button>\n      </div>\n      <Carousel v-model=\"index\" trigger=\"hover\" autoplay :autoplay-speed=\"6000\" class=\"contest\">\n        <CarouselItem v-for=\"(contest, index) of contests\" :key=\"index\">\n          <div class=\"contest-content\">\n            <div class=\"contest-content-tags\">\n              <Button type=\"info\" shape=\"circle\" size=\"small\" icon=\"calendar\">\n                {{contest.start_time | localtime('YYYY-M-D HH:mm') }}\n              </Button>\n              <Button type=\"success\" shape=\"circle\" size=\"small\" icon=\"android-time\">\n                {{getDuration(contest.start_time, contest.end_time)}}\n              </Button>\n              <Button type=\"warning\" shape=\"circle\" size=\"small\" icon=\"trophy\">\n                {{contest.rule_type}}\n              </Button>\n            </div>\n            <div class=\"contest-content-description\">\n              <blockquote v-html=\"contest.description\"></blockquote>\n            </div>\n          </div>\n        </CarouselItem>\n      </Carousel>\n    </panel>\n    <Announcements class=\"announcement\"></Announcements>\n    </Col>\n  </Row>\n</template>\n\n<script>\n  import Announcements from './Announcements.vue'\n  import api from '@oj/api'\n  import time from '@/utils/time'\n  import { CONTEST_STATUS } from '@/utils/constants'\n\n  export default {\n    name: 'home',\n    components: {\n      Announcements\n    },\n    data () {\n      return {\n        contests: [],\n        index: 0\n      }\n    },\n    mounted () {\n      let params = {status: CONTEST_STATUS.NOT_START}\n      api.getContestList(0, 5, params).then(res => {\n        this.contests = res.data.data.results\n      })\n    },\n    methods: {\n      getDuration (startTime, endTime) {\n        return time.duration(startTime, endTime)\n      },\n      goContest () {\n        this.$router.push({\n          name: 'contest-details',\n          params: {contestID: this.contests[this.index].id}\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .contest {\n    &-title {\n      font-style: italic;\n      font-size: 21px;\n    }\n    &-content {\n      padding: 0 70px 40px 70px;\n      &-description {\n        margin-top: 25px;\n      }\n    }\n  }\n\n  .announcement {\n    margin-top: 20px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/help/About.vue",
    "content": "<template>\n  <div>\n    <panel class=\"container\">\n      <div slot=\"title\">{{$t('m.Compiler')}} & {{$t('m.Judger')}}</div>\n      <div class=\"content markdown-body\">\n        <ul>\n          <li v-for=\"lang in languages\">{{lang.name}} ( {{lang.description}} )\n            <pre>{{lang.config.compile.compile_command}}</pre>\n          </li>\n        </ul>\n      </div>\n    </panel>\n\n    <panel :padding=\"15\" class=\"container\">\n      <div slot=\"title\">{{$t('m.Result_Explanation')}}</div>\n      <div class=\"content\">\n        <ul>\n          <li><b>{{$t('m.Pending')}} & {{$t('m.Judging')}}</b> : {{$t('m.Pending_Judging_Description')}}</li>\n          <li><b>{{$t('m.Compile_Error')}}</b> :\t{{$t('m.Compile_Error_Description')}}\n      </li>\n          <li><b>{{$t('m.Accepted')}}</b> :\t{{$t('m.Accepted_Description')}}</li>\n          <li><b>{{$t('m.Wrong_Answer')}}</b> :\t{{$t('m.Wrong_Answer_Description')}}</li>\n          <li>\n            <b>{{$t('m.Runtime_Error')}}</b>\n            :\t{{$t('m.Runtime_Error_Description')}}\n          </li>\n          <li><b>{{$t('m.Time_Limit_Exceeded')}}</b>\n            :\t{{$t('m.Time_Limit_Exceeded_Description')}}\n          </li>\n          <li><b>{{$t('m.Memory_Limit_Exceeded')}}</b> :\t{{$t('m.Memory_Limit_Exceeded_Description')}}</li>\n          <li><b>{{$t('m.System_Error')}}</b> :\t{{$t('m.System_Error_Description')}}\n          </li>\n        </ul>\n      </div>\n    </panel>\n\n  </div>\n</template>\n\n<script>\n  import utils from '@/utils/utils'\n\n  export default {\n    data () {\n      return {\n        languages: []\n      }\n    },\n    beforeRouteEnter (to, from, next) {\n      utils.getLanguages().then(languages => {\n        next(vm => {\n          vm.languages = languages\n        })\n      })\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .container {\n    margin-bottom: 20px;\n\n    .content {\n      font-size: 16px;\n      margin: 0 50px 20px 50px;\n      > ul {\n        list-style: disc;\n        li {\n          line-height: 2;\n          .title {\n            font-weight: 500;\n          }\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/help/FAQ.vue",
    "content": "<template>\n  <panel>\n    <div slot=\"title\">{{$t('m.Frequently_Asked_Questions')}}</div>\n    <div class=\"content markdown-body\">\n      <ul>\n        <li>{{$t('m.Where_is_the_input_and_the_output')}} \n          <p>{{$t('m.Where_is_the_input_and_the_output_answer_part_1')}} <code>stdin</code> ('{{$t('m.Standard_Input')}}') {{$t('m.Where_is_the_input_and_the_output_answer_part_3')}} <code>stdout</code>\n            ('{{$t('m.Standard_Output')}}') {{$t('m.Where_is_the_input_and_the_output_answer_part_5')}} <code>scanf</code> {{$t('m.Where_is_the_input_and_the_output_answer_part_6')}} <code>cin</code>\n            {{$t('m.Where_is_the_input_and_the_output_answer_part_7')}} <code>printf</code> {{$t('m.Where_is_the_input_and_the_output_answer_part_8')}} <code>cout</code> {{$t('m.Where_is_the_input_and_the_output_answer_part_9')}} <code>{{$t('m.Runtime_Error')}}</code>.\n          </p>\n        </li>\n        <li>{{$t('m.What_is_the_meaning_of_submission_execution_time')}} \n        <p>{{$t('m.What_is_the_meaning_of_submission_execution_time_answer')}} \n        </p>\n        </li>\n        <li>{{$t('m.How_Can_I_use_CPP_Int64')}} \n          <p>{{$t('m.How_Can_I_use_CPP_Int64_answer_part_1')}}<code>long long</code> {{$t('m.How_Can_I_use_CPP_Int64_answer_part_2')}} <code>cin/cout</code> {{$t('m.or')}} <code>%lld</code>, {{$t('m.using')}}<code> __int64</code> {{$t('m.How_Can_I_use_CPP_Int64_answer_part_3')}} <code>{{$t('m.Compile_Error')}}</code>.</p>\n        </li>\n        <li>{{$t('m.Java_specifications')}}\n          <p>{{$t('m.Java_specifications_answer_part_1')}} <code>Main</code> {{$t('m.Java_specifications_answer_part_2')}} <code>Main</code> {{$t('m.Java_specifications_answer_part_3')}}</p>\n        </li>\n        <li>{{$t('m.About_presentation_error')}}\n          <p>{{$t('m.About_presentation_error_answer_part_1')}} <b>{{$t('m.last')}}</b> {{$t('m.About_presentation_error_answer_part_2')}} <code> {{$t('m.Wrong_Answer')}}</code>.</p>\n        </li>\n        <li>{{$t('m.How_to_report_bugs')}}\n          <p>{{$t('m.How_to_report_bugs_answer_part_1')}} <a href=\"https://github.com/QingdaoU/OnlineJudge\">Github</a>\n            {{$t('m.How_to_report_bugs_answer_part_2')}}\n          </p>\n        </li>\n      </ul>\n    </div>\n  </panel>\n</template>\n\n<script>\n</script>\n\n<style lang=\"less\" scoped>\n  .content {\n    font-size: 16px;\n    margin: 0 50px 40px 50px;\n    > ul {\n      list-style: disc;\n      li {\n        font-size: 16px;\n        margin-top: 20px;\n        &:first-child {\n          margin-top: 0;\n        }\n        p {\n          font-size: 14px;\n          margin-top: 5px;\n        }\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/index.js",
    "content": "import ProblemList from './problem/ProblemList.vue'\nimport Logout from './user/Logout.vue'\nimport UserHome from './user/UserHome.vue'\nimport About from './help/About.vue'\nimport FAQ from './help/FAQ.vue'\nimport NotFound from './general/404.vue'\nimport Home from './general/Home.vue'\nimport Announcements from './general/Announcements.vue'\n\n// Grouping Components in the Same Chunk\nconst SubmissionList = () => import(/* webpackChunkName: \"submission\" */ '@oj/views/submission/SubmissionList.vue')\nconst SubmissionDetails = () => import(/* webpackChunkName: \"submission\" */ '@oj/views/submission/SubmissionDetails.vue')\n\nconst ACMRank = () => import(/* webpackChunkName: \"userRank\" */ '@oj/views/rank/ACMRank.vue')\nconst OIRank = () => import(/* webpackChunkName: \"userRank\" */ '@oj/views/rank/OIRank.vue')\n\nconst ApplyResetPassword = () => import(/* webpackChunkName: \"password\" */ '@oj/views/user/ApplyResetPassword.vue')\nconst ResetPassword = () => import(/* webpackChunkName: \"password\" */ '@oj/views/user/ResetPassword.vue')\n\nconst Problem = () => import(/* webpackChunkName: \"Problem\" */ '@oj/views/problem/Problem.vue')\n\nexport {\n  Home, NotFound, Announcements,\n  Logout, UserHome, About, FAQ,\n  ProblemList, Problem,\n  ACMRank, OIRank,\n  SubmissionList, SubmissionDetails,\n  ApplyResetPassword, ResetPassword\n}\n/* 组件导出分为两类, 一类常用的直接导出，另一类诸如Login, Logout等用懒加载,懒加载不在此处导出\n *   在对应的route内加载\n *   见https://router.vuejs.org/en/advanced/lazy-loading.html\n */\n"
  },
  {
    "path": "src/pages/oj/views/problem/Problem.vue",
    "content": "<template>\n  <div class=\"flex-container\">\n    <div id=\"problem-main\">\n      <!--problem main-->\n      <Panel :padding=\"40\" shadow>\n        <div slot=\"title\">{{problem.title}}</div>\n        <div id=\"problem-content\" class=\"markdown-body\" v-katex>\n          <p class=\"title\">{{$t('m.Description')}}</p>\n          <p class=\"content\" v-html=problem.description></p>\n          <!-- {{$t('m.music')}} -->\n          <p class=\"title\">{{$t('m.Input')}} <span v-if=\"problem.io_mode.io_mode=='File IO'\">({{$t('m.FromFile')}}: {{ problem.io_mode.input }})</span></p>\n          <p class=\"content\" v-html=problem.input_description></p>\n\n          <p class=\"title\">{{$t('m.Output')}} <span v-if=\"problem.io_mode.io_mode=='File IO'\">({{$t('m.ToFile')}}: {{ problem.io_mode.output }})</span></p>\n          <p class=\"content\" v-html=problem.output_description></p>\n\n          <div v-for=\"(sample, index) of problem.samples\" :key=\"index\">\n            <div class=\"flex-container sample\">\n              <div class=\"sample-input\">\n                <p class=\"title\">{{$t('m.Sample_Input')}} {{index + 1}}\n                  <a class=\"copy\"\n                     v-clipboard:copy=\"sample.input\"\n                     v-clipboard:success=\"onCopy\"\n                     v-clipboard:error=\"onCopyError\">\n                    <Icon type=\"clipboard\"></Icon>\n                  </a>\n                </p>\n                <pre>{{sample.input}}</pre>\n              </div>\n              <div class=\"sample-output\">\n                <p class=\"title\">{{$t('m.Sample_Output')}} {{index + 1}}</p>\n                <pre>{{sample.output}}</pre>\n              </div>\n            </div>\n          </div>\n\n          <div v-if=\"problem.hint\">\n            <p class=\"title\">{{$t('m.Hint')}}</p>\n            <Card dis-hover>\n              <div class=\"content\" v-html=problem.hint></div>\n            </Card>\n          </div>\n\n          <div v-if=\"problem.source\">\n            <p class=\"title\">{{$t('m.Source')}}</p>\n            <p class=\"content\">{{problem.source}}</p>\n          </div>\n\n        </div>\n      </Panel>\n      <!--problem main end-->\n      <Card :padding=\"20\" id=\"submit-code\" dis-hover>\n        <CodeMirror :value.sync=\"code\"\n                    :languages=\"problem.languages\"\n                    :language=\"language\"\n                    :theme=\"theme\"\n                    @resetCode=\"onResetToTemplate\"\n                    @changeTheme=\"onChangeTheme\"\n                    @changeLang=\"onChangeLang\"></CodeMirror>\n        <Row type=\"flex\" justify=\"space-between\">\n          <Col :span=\"10\">\n            <div class=\"status\" v-if=\"statusVisible\">\n              <template v-if=\"!this.contestID || (this.contestID && OIContestRealTimePermission)\">\n                <span>{{$t('m.Status')}}</span>\n                <Tag type=\"dot\" :color=\"submissionStatus.color\" @click.native=\"handleRoute('/status/'+submissionId)\">\n                  {{$t('m.' + submissionStatus.text.replace(/ /g, \"_\"))}}\n                </Tag>\n              </template>\n              <template v-else-if=\"this.contestID && !OIContestRealTimePermission\">\n                <Alert type=\"success\" show-icon>{{$t('m.Submitted_successfully')}}</Alert>\n              </template>\n            </div>\n            <div v-else-if=\"problem.my_status === 0\">\n              <Alert type=\"success\" show-icon>{{$t('m.You_have_solved_the_problem')}}</Alert>\n            </div>\n            <div v-else-if=\"this.contestID && !OIContestRealTimePermission && submissionExists\">\n              <Alert type=\"success\" show-icon>{{$t('m.You_have_submitted_a_solution')}}</Alert>\n            </div>\n            <div v-if=\"contestEnded\">\n              <Alert type=\"warning\" show-icon>{{$t('m.Contest_has_ended')}}</Alert>\n            </div>\n          </Col>\n\n          <Col :span=\"12\">\n            <template v-if=\"captchaRequired\">\n              <div class=\"captcha-container\">\n                <Tooltip v-if=\"captchaRequired\" content=\"Click to refresh\" placement=\"top\">\n                  <img :src=\"captchaSrc\" @click=\"getCaptchaSrc\"/>\n                </Tooltip>\n                <Input v-model=\"captchaCode\" class=\"captcha-code\"/>\n              </div>\n            </template>\n            <Button type=\"warning\" icon=\"edit\" :loading=\"submitting\" @click=\"submitCode\"\n                    :disabled=\"problemSubmitDisabled || submitted\"\n                    class=\"fl-right\">\n              <span v-if=\"submitting\">{{$t('m.Submitting')}}</span>\n              <span v-else>{{$t('m.Submit')}}</span>\n            </Button>\n          </Col>\n        </Row>\n      </Card>\n    </div>\n\n    <div id=\"right-column\">\n      <VerticalMenu @on-click=\"handleRoute\">\n        <template v-if=\"this.contestID\">\n          <VerticalMenu-item :route=\"{name: 'contest-problem-list', params: {contestID: contestID}}\">\n            <Icon type=\"ios-photos\"></Icon>\n            {{$t('m.Problems')}}\n          </VerticalMenu-item>\n\n          <VerticalMenu-item :route=\"{name: 'contest-announcement-list', params: {contestID: contestID}}\">\n            <Icon type=\"chatbubble-working\"></Icon>\n            {{$t('m.Announcements')}}\n          </VerticalMenu-item>\n        </template>\n\n        <VerticalMenu-item v-if=\"!this.contestID || OIContestRealTimePermission\" :route=\"submissionRoute\">\n          <Icon type=\"navicon-round\"></Icon>\n           {{$t('m.Submissions')}}\n        </VerticalMenu-item>\n\n        <template v-if=\"this.contestID\">\n          <VerticalMenu-item v-if=\"!this.contestID || OIContestRealTimePermission\"\n                             :route=\"{name: 'contest-rank', params: {contestID: contestID}}\">\n            <Icon type=\"stats-bars\"></Icon>\n            {{$t('m.Rankings')}}\n          </VerticalMenu-item>\n          <VerticalMenu-item :route=\"{name: 'contest-details', params: {contestID: contestID}}\">\n            <Icon type=\"home\"></Icon>\n            {{$t('m.View_Contest')}}\n          </VerticalMenu-item>\n        </template>\n      </VerticalMenu>\n\n      <Card id=\"info\">\n        <div slot=\"title\" class=\"header\">\n          <Icon type=\"information-circled\"></Icon>\n          <span class=\"card-title\">{{$t('m.Information')}}</span>\n        </div>\n        <ul>\n          <li><p>ID</p>\n            <p>{{problem._id}}</p></li>\n          <li>\n            <p>{{$t('m.Time_Limit')}}</p>\n            <p>{{problem.time_limit}}MS</p></li>\n          <li>\n            <p>{{$t('m.Memory_Limit')}}</p>\n            <p>{{problem.memory_limit}}MB</p></li>\n          <li>\n          <li>\n            <p>{{$t('m.IOMode')}}</p>\n            <p>{{problem.io_mode.io_mode}}</p>\n          </li>\n          <li>\n            <p>{{$t('m.Created')}}</p>\n            <p>{{problem.created_by.username}}</p></li>\n          <li v-if=\"problem.difficulty\">\n            <p>{{$t('m.Level')}}</p>\n            <p>{{$t('m.' + problem.difficulty)}}</p></li>\n          <li v-if=\"problem.total_score\">\n            <p>{{$t('m.Score')}}</p>\n            <p>{{problem.total_score}}</p>\n          </li>\n          <li>\n            <p>{{$t('m.Tags')}}</p>\n            <p>\n              <Poptip trigger=\"hover\" placement=\"left-end\">\n                <a>{{$t('m.Show')}}</a>\n                <div slot=\"content\">\n                  <Tag v-for=\"tag in problem.tags\" :key=\"tag\">{{tag}}</Tag>\n                </div>\n              </Poptip>\n            </p>\n          </li>\n        </ul>\n      </Card>\n\n      <Card id=\"pieChart\" :padding=\"0\" v-if=\"!this.contestID || OIContestRealTimePermission\">\n        <div slot=\"title\">\n          <Icon type=\"ios-analytics\"></Icon>\n          <span class=\"card-title\">{{$t('m.Statistic')}}</span>\n          <Button type=\"ghost\" size=\"small\" id=\"detail\" @click=\"graphVisible = !graphVisible\">Details</Button>\n        </div>\n        <div class=\"echarts\">\n          <ECharts :options=\"pie\"></ECharts>\n        </div>\n      </Card>\n    </div>\n\n    <Modal v-model=\"graphVisible\">\n      <div id=\"pieChart-detail\">\n        <ECharts :options=\"largePie\" :initOptions=\"largePieInitOpts\"></ECharts>\n      </div>\n      <div slot=\"footer\">\n        <Button type=\"ghost\" @click=\"graphVisible=false\">{{$t('m.Close')}}</Button>\n      </div>\n    </Modal>\n  </div>\n</template>\n\n<script>\n  import {mapGetters, mapActions} from 'vuex'\n  import {types} from '../../../../store'\n  import CodeMirror from '@oj/components/CodeMirror.vue'\n  import storage from '@/utils/storage'\n  import {FormMixin} from '@oj/components/mixins'\n  import {JUDGE_STATUS, CONTEST_STATUS, buildProblemCodeKey} from '@/utils/constants'\n  import api from '@oj/api'\n  import {pie, largePie} from './chartData'\n\n  // 只显示这些状态的图形占用\n  const filtedStatus = ['-1', '-2', '0', '1', '2', '3', '4', '8']\n\n  export default {\n    name: 'Problem',\n    components: {\n      CodeMirror\n    },\n    mixins: [FormMixin],\n    data () {\n      return {\n        statusVisible: false,\n        captchaRequired: false,\n        graphVisible: false,\n        submissionExists: false,\n        captchaCode: '',\n        captchaSrc: '',\n        contestID: '',\n        problemID: '',\n        submitting: false,\n        code: '',\n        language: 'C++',\n        theme: 'solarized',\n        submissionId: '',\n        submitted: false,\n        result: {\n          result: 9\n        },\n        problem: {\n          title: '',\n          description: '',\n          hint: '',\n          my_status: '',\n          template: {},\n          languages: [],\n          created_by: {\n            username: ''\n          },\n          tags: [],\n          io_mode: {'io_mode': 'Standard IO'}\n        },\n        pie: pie,\n        largePie: largePie,\n        // echarts 无法获取隐藏dom的大小，需手动指定\n        largePieInitOpts: {\n          width: '500',\n          height: '480'\n        }\n      }\n    },\n    beforeRouteEnter (to, from, next) {\n      let problemCode = storage.get(buildProblemCodeKey(to.params.problemID, to.params.contestID))\n      if (problemCode) {\n        next(vm => {\n          vm.language = problemCode.language\n          vm.code = problemCode.code\n          vm.theme = problemCode.theme\n        })\n      } else {\n        next()\n      }\n    },\n    mounted () {\n      this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {menu: false})\n      this.init()\n    },\n    methods: {\n      ...mapActions(['changeDomTitle']),\n      init () {\n        this.$Loading.start()\n        this.contestID = this.$route.params.contestID\n        this.problemID = this.$route.params.problemID\n        let func = this.$route.name === 'problem-details' ? 'getProblem' : 'getContestProblem'\n        api[func](this.problemID, this.contestID).then(res => {\n          this.$Loading.finish()\n          let problem = res.data.data\n          this.changeDomTitle({title: problem.title})\n          api.submissionExists(problem.id).then(res => {\n            this.submissionExists = res.data.data\n          })\n          problem.languages = problem.languages.sort()\n          this.problem = problem\n          if (problem.statistic_info) {\n            this.changePie(problem)\n          }\n\n          // 在beforeRouteEnter中修改了, 说明本地有code，无需加载template\n          if (this.code !== '') {\n            return\n          }\n          // try to load problem template\n          this.language = this.problem.languages[0]\n          let template = this.problem.template\n          if (template && template[this.language]) {\n            this.code = template[this.language]\n          }\n        }, () => {\n          this.$Loading.error()\n        })\n      },\n      changePie (problemData) {\n        // 只显示特定的一些状态\n        for (let k in problemData.statistic_info) {\n          if (filtedStatus.indexOf(k) === -1) {\n            delete problemData.statistic_info[k]\n          }\n        }\n        let acNum = problemData.accepted_number\n        let data = [\n          {name: 'WA', value: problemData.submission_number - acNum},\n          {name: 'AC', value: acNum}\n        ]\n        this.pie.series[0].data = data\n        // 只把大图的AC selected下，这里需要做一下deepcopy\n        let data2 = JSON.parse(JSON.stringify(data))\n        data2[1].selected = true\n        this.largePie.series[1].data = data2\n\n        // 根据结果设置legend,没有提交过的legend不显示\n        let legend = Object.keys(problemData.statistic_info).map(ele => JUDGE_STATUS[ele].short)\n        if (legend.length === 0) {\n          legend.push('AC', 'WA')\n        }\n        this.largePie.legend.data = legend\n\n        // 把ac的数据提取出来放在最后\n        let acCount = problemData.statistic_info['0']\n        delete problemData.statistic_info['0']\n\n        let largePieData = []\n        Object.keys(problemData.statistic_info).forEach(ele => {\n          largePieData.push({name: JUDGE_STATUS[ele].short, value: problemData.statistic_info[ele]})\n        })\n        largePieData.push({name: 'AC', value: acCount})\n        this.largePie.series[0].data = largePieData\n      },\n      handleRoute (route) {\n        this.$router.push(route)\n      },\n      onChangeLang (newLang) {\n        if (this.problem.template[newLang]) {\n          if (this.code.trim() === '') {\n            this.code = this.problem.template[newLang]\n          }\n        }\n        this.language = newLang\n      },\n      onChangeTheme (newTheme) {\n        this.theme = newTheme\n      },\n      onResetToTemplate () {\n        this.$Modal.confirm({\n          content: this.$i18n.t('m.Are_you_sure_you_want_to_reset_your_code'),\n          onOk: () => {\n            let template = this.problem.template\n            if (template && template[this.language]) {\n              this.code = template[this.language]\n            } else {\n              this.code = ''\n            }\n          }\n        })\n      },\n      checkSubmissionStatus () {\n        // 使用setTimeout避免一些问题\n        if (this.refreshStatus) {\n          // 如果之前的提交状态检查还没有停止,则停止,否则将会失去timeout的引用造成无限请求\n          clearTimeout(this.refreshStatus)\n        }\n        const checkStatus = () => {\n          let id = this.submissionId\n          api.getSubmission(id).then(res => {\n            this.result = res.data.data\n            if (Object.keys(res.data.data.statistic_info).length !== 0) {\n              this.submitting = false\n              this.submitted = false\n              clearTimeout(this.refreshStatus)\n              this.init()\n            } else {\n              this.refreshStatus = setTimeout(checkStatus, 2000)\n            }\n          }, res => {\n            this.submitting = false\n            clearTimeout(this.refreshStatus)\n          })\n        }\n        this.refreshStatus = setTimeout(checkStatus, 2000)\n      },\n      submitCode () {\n        if (this.code.trim() === '') {\n          this.$error(this.$i18n.t('m.Code_can_not_be_empty'))\n          return\n        }\n        this.submissionId = ''\n        this.result = {result: 9}\n        this.submitting = true\n        let data = {\n          problem_id: this.problem.id,\n          language: this.language,\n          code: this.code,\n          contest_id: this.contestID\n        }\n        if (this.captchaRequired) {\n          data.captcha = this.captchaCode\n        }\n        const submitFunc = (data, detailsVisible) => {\n          this.statusVisible = true\n          api.submitCode(data).then(res => {\n            this.submissionId = res.data.data && res.data.data.submission_id\n            // 定时检查状态\n            this.submitting = false\n            this.submissionExists = true\n            if (!detailsVisible) {\n              this.$Modal.success({\n                title: this.$i18n.t('m.Success'),\n                content: this.$i18n.t('m.Submit_code_successfully')\n              })\n              return\n            }\n            this.submitted = true\n            this.checkSubmissionStatus()\n          }, res => {\n            this.getCaptchaSrc()\n            if (res.data.data.startsWith('Captcha is required')) {\n              this.captchaRequired = true\n            }\n            this.submitting = false\n            this.statusVisible = false\n          })\n        }\n\n        if (this.contestRuleType === 'OI' && !this.OIContestRealTimePermission) {\n          if (this.submissionExists) {\n            this.$Modal.confirm({\n              title: '',\n              content: '<h3>' + this.$i18n.t('m.You_have_submission_in_this_problem_sure_to_cover_it') + '<h3>',\n              onOk: () => {\n                // 暂时解决对话框与后面提示对话框冲突的问题(否则一闪而过）\n                setTimeout(() => {\n                  submitFunc(data, false)\n                }, 1000)\n              },\n              onCancel: () => {\n                this.submitting = false\n              }\n            })\n          } else {\n            submitFunc(data, false)\n          }\n        } else {\n          submitFunc(data, true)\n        }\n      },\n      onCopy (event) {\n        this.$success('Code copied')\n      },\n      onCopyError (e) {\n        this.$error('Failed to copy code')\n      }\n    },\n    computed: {\n      ...mapGetters(['problemSubmitDisabled', 'contestRuleType', 'OIContestRealTimePermission', 'contestStatus']),\n      contest () {\n        return this.$store.state.contest.contest\n      },\n      contestEnded () {\n        return this.contestStatus === CONTEST_STATUS.ENDED\n      },\n      submissionStatus () {\n        return {\n          text: JUDGE_STATUS[this.result.result]['name'],\n          color: JUDGE_STATUS[this.result.result]['color']\n        }\n      },\n      submissionRoute () {\n        if (this.contestID) {\n          return {name: 'contest-submission-list', query: {problemID: this.problemID}}\n        } else {\n          return {name: 'submission-list', query: {problemID: this.problemID}}\n        }\n      }\n    },\n    beforeRouteLeave (to, from, next) {\n      // 防止切换组件后仍然不断请求\n      clearInterval(this.refreshStatus)\n\n      this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {menu: true})\n      storage.set(buildProblemCodeKey(this.problem._id, from.params.contestID), {\n        code: this.code,\n        language: this.language,\n        theme: this.theme\n      })\n      next()\n    },\n    watch: {\n      '$route' () {\n        this.init()\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .card-title {\n    margin-left: 8px;\n  }\n\n  .flex-container {\n    #problem-main {\n      flex: auto;\n      margin-right: 18px;\n    }\n    #right-column {\n      flex: none;\n      width: 220px;\n    }\n  }\n\n  #problem-content {\n    margin-top: -50px;\n    .title {\n      font-size: 20px;\n      font-weight: 400;\n      margin: 25px 0 8px 0;\n      color: #3091f2;\n      .copy {\n        padding-left: 8px;\n      }\n    }\n    p.content {\n      margin-left: 25px;\n      margin-right: 20px;\n      font-size: 15px\n    }\n    .sample {\n      align-items: stretch;\n      &-input, &-output {\n        width: 50%;\n        flex: 1 1 auto;\n        display: flex;\n        flex-direction: column;\n        margin-right: 5%;\n      }\n      pre {\n        flex: 1 1 auto;\n        align-self: stretch;\n        border-style: solid;\n        background: transparent;\n      }\n    }\n  }\n\n  #submit-code {\n    margin-top: 20px;\n    margin-bottom: 20px;\n    .status {\n      float: left;\n      span {\n        margin-right: 10px;\n        margin-left: 10px;\n      }\n    }\n    .captcha-container {\n      display: inline-block;\n      .captcha-code {\n        width: auto;\n        margin-top: -20px;\n        margin-left: 20px;\n      }\n    }\n  }\n\n  #info {\n    margin-bottom: 20px;\n    margin-top: 20px;\n    ul {\n      list-style-type: none;\n      li {\n        border-bottom: 1px dotted #e9eaec;\n        margin-bottom: 10px;\n        p {\n          display: inline-block;\n        }\n        p:first-child {\n          width: 90px;\n        }\n        p:last-child {\n          float: right;\n        }\n      }\n    }\n  }\n\n  .fl-right {\n    float: right;\n  }\n\n  #pieChart {\n    .echarts {\n      height: 250px;\n      width: 210px;\n    }\n    #detail {\n      position: absolute;\n      right: 10px;\n      top: 10px;\n    }\n  }\n\n  #pieChart-detail {\n    margin-top: 20px;\n    width: 500px;\n    height: 480px;\n  }\n</style>\n\n"
  },
  {
    "path": "src/pages/oj/views/problem/ProblemList.vue",
    "content": "<template>\n  <Row type=\"flex\" :gutter=\"18\">\n    <Col :span=19>\n    <Panel shadow>\n      <div slot=\"title\">{{$t('m.Problem_List')}}</div>\n      <div slot=\"extra\">\n        <ul class=\"filter\">\n          <li>\n            <Dropdown @on-click=\"filterByDifficulty\">\n              <span>{{query.difficulty === '' ? this.$i18n.t('m.Difficulty') : this.$i18n.t('m.' + query.difficulty)}}\n                <Icon type=\"arrow-down-b\"></Icon>\n              </span>\n              <Dropdown-menu slot=\"list\">\n                <Dropdown-item name=\"\">{{$t('m.All')}}</Dropdown-item>\n                <Dropdown-item name=\"Low\">{{$t('m.Low')}}</Dropdown-item>\n                <Dropdown-item name=\"Mid\" >{{$t('m.Mid')}}</Dropdown-item>\n                <Dropdown-item name=\"High\">{{$t('m.High')}}</Dropdown-item>\n              </Dropdown-menu>\n            </Dropdown>\n          </li>\n          <li>\n            <i-switch size=\"large\" @on-change=\"handleTagsVisible\">\n              <span slot=\"open\">{{$t('m.Tags')}}</span>\n              <span slot=\"close\">{{$t('m.Tags')}}</span>\n            </i-switch>\n          </li>\n          <li>\n            <Input v-model=\"query.keyword\"\n                   @on-enter=\"filterByKeyword\"\n                   @on-click=\"filterByKeyword\"\n                   placeholder=\"keyword\"\n                   icon=\"ios-search-strong\"/>\n          </li>\n          <li>\n            <Button type=\"info\" @click=\"onReset\">\n              <Icon type=\"refresh\"></Icon>\n              {{$t('m.Reset')}}\n            </Button>\n          </li>\n        </ul>\n      </div>\n      <Table style=\"width: 100%; font-size: 16px;\"\n             :columns=\"problemTableColumns\"\n             :data=\"problemList\"\n             :loading=\"loadings.table\"\n             disabled-hover></Table>\n    </Panel>\n    <Pagination\n      :total=\"total\" :page-size.sync=\"query.limit\" @on-change=\"pushRouter\" @on-page-size-change=\"pushRouter\" :current.sync=\"query.page\" :show-sizer=\"true\"></Pagination>\n\n    </Col>\n\n    <Col :span=\"5\">\n    <Panel :padding=\"10\">\n      <div slot=\"title\" class=\"taglist-title\">{{$t('m.Tags')}}</div>\n      <Button v-for=\"tag in tagList\"\n              :key=\"tag.name\"\n              @click=\"filterByTag(tag.name)\"\n              type=\"ghost\"\n              :disabled=\"query.tag === tag.name\"\n              shape=\"circle\"\n              class=\"tag-btn\">{{tag.name}}\n      </Button>\n\n      <Button long id=\"pick-one\" @click=\"pickone\">\n        <Icon type=\"shuffle\"></Icon>\n        {{$t('m.Pick_One')}}\n      </Button>\n    </Panel>\n    <Spin v-if=\"loadings.tag\" fix size=\"large\"></Spin>\n    </Col>\n  </Row>\n</template>\n\n<script>\n  import { mapGetters } from 'vuex'\n  import api from '@oj/api'\n  import utils from '@/utils/utils'\n  import { ProblemMixin } from '@oj/components/mixins'\n  import Pagination from '@oj/components/Pagination'\n\n  export default {\n    name: 'ProblemList',\n    mixins: [ProblemMixin],\n    components: {\n      Pagination\n    },\n    data () {\n      return {\n        tagList: [],\n        problemTableColumns: [\n          {\n            title: '#',\n            key: '_id',\n            width: 80,\n            render: (h, params) => {\n              return h('Button', {\n                props: {\n                  type: 'text',\n                  size: 'large'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push({name: 'problem-details', params: {problemID: params.row._id}})\n                  }\n                },\n                style: {\n                  padding: '2px 0'\n                }\n              }, params.row._id)\n            }\n          },\n          {\n            title: this.$i18n.t('m.Title'),\n            width: 400,\n            render: (h, params) => {\n              return h('Button', {\n                props: {\n                  type: 'text',\n                  size: 'large'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push({name: 'problem-details', params: {problemID: params.row._id}})\n                  }\n                },\n                style: {\n                  padding: '2px 0',\n                  overflowX: 'auto',\n                  textAlign: 'left',\n                  width: '100%'\n                }\n              }, params.row.title)\n            }\n          },\n          {\n            title: this.$i18n.t('m.Level'),\n            render: (h, params) => {\n              let t = params.row.difficulty\n              let color = 'blue'\n              if (t === 'Low') color = 'green'\n              else if (t === 'High') color = 'yellow'\n              return h('Tag', {\n                props: {\n                  color: color\n                }\n              }, this.$i18n.t('m.' + params.row.difficulty))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Total'),\n            key: 'submission_number'\n          },\n          {\n            title: this.$i18n.t('m.AC_Rate'),\n            render: (h, params) => {\n              return h('span', this.getACRate(params.row.accepted_number, params.row.submission_number))\n            }\n          }\n        ],\n        problemList: [],\n        limit: 20,\n        total: 0,\n        loadings: {\n          table: true,\n          tag: true\n        },\n        routeName: '',\n        query: {\n          keyword: '',\n          difficulty: '',\n          tag: '',\n          page: 1,\n          limit: 10\n        }\n      }\n    },\n    mounted () {\n      this.init()\n    },\n    methods: {\n      init (simulate = false) {\n        this.routeName = this.$route.name\n        let query = this.$route.query\n        this.query.difficulty = query.difficulty || ''\n        this.query.keyword = query.keyword || ''\n        this.query.tag = query.tag || ''\n        this.query.page = parseInt(query.page) || 1\n        if (this.query.page < 1) {\n          this.query.page = 1\n        }\n        this.query.limit = parseInt(query.limit) || 10\n        if (!simulate) {\n          this.getTagList()\n        }\n        this.getProblemList()\n      },\n      pushRouter () {\n        this.$router.push({\n          name: 'problem-list',\n          query: utils.filterEmptyValue(this.query)\n        })\n      },\n      getProblemList () {\n        let offset = (this.query.page - 1) * this.query.limit\n        this.loadings.table = true\n        api.getProblemList(offset, this.limit, this.query).then(res => {\n          this.loadings.table = false\n          this.total = res.data.data.total\n          this.problemList = res.data.data.results\n          if (this.isAuthenticated) {\n            this.addStatusColumn(this.problemTableColumns, res.data.data.results)\n          }\n        }, res => {\n          this.loadings.table = false\n        })\n      },\n      getTagList () {\n        api.getProblemTagList().then(res => {\n          this.tagList = res.data.data\n          this.loadings.tag = false\n        }, res => {\n          this.loadings.tag = false\n        })\n      },\n      filterByTag (tagName) {\n        this.query.tag = tagName\n        this.query.page = 1\n        this.pushRouter()\n      },\n      filterByDifficulty (difficulty) {\n        this.query.difficulty = difficulty\n        this.query.page = 1\n        this.pushRouter()\n      },\n      filterByKeyword () {\n        this.query.page = 1\n        this.pushRouter()\n      },\n      handleTagsVisible (value) {\n        if (value) {\n          this.problemTableColumns.push(\n            {\n              title: this.$i18n.t('m.Tags'),\n              align: 'center',\n              render: (h, params) => {\n                let tags = []\n                params.row.tags.forEach(tag => {\n                  tags.push(h('Tag', {}, tag))\n                })\n                return h('div', {\n                  style: {\n                    margin: '8px 0'\n                  }\n                }, tags)\n              }\n            })\n        } else {\n          this.problemTableColumns.splice(this.problemTableColumns.length - 1, 1)\n        }\n      },\n      onReset () {\n        this.$router.push({name: 'problem-list'})\n      },\n      pickone () {\n        api.pickone().then(res => {\n          this.$success('Good Luck')\n          this.$router.push({name: 'problem-details', params: {problemID: res.data.data}})\n        })\n      }\n    },\n    computed: {\n      ...mapGetters(['isAuthenticated'])\n    },\n    watch: {\n      '$route' (newVal, oldVal) {\n        if (newVal !== oldVal) {\n          this.init(true)\n        }\n      },\n      'isAuthenticated' (newVal) {\n        if (newVal === true) {\n          this.init()\n        }\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .taglist-title {\n    margin-left: -10px;\n    margin-bottom: -10px;\n  }\n\n  .tag-btn {\n    margin-right: 5px;\n    margin-bottom: 10px;\n  }\n\n  #pick-one {\n    margin-top: 10px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/problem/chartData.js",
    "content": "const pieColorMap = {\n  'AC': {color: '#19be6b'},\n  'WA': {color: '#ed3f14'},\n  'TLE': {color: '#ff9300'},\n  'MLE': {color: '#f7de00'},\n  'RE': {color: '#ff6104'},\n  'CE': {color: '#80848f'},\n  'PAC': {color: '#2d8cf0'}\n}\n\nfunction getItemColor (obj) {\n  return pieColorMap[obj.name].color\n}\n\nconst pie = {\n  legend: {\n    left: 'center',\n    top: '10',\n    orient: 'horizontal',\n    data: ['AC', 'WA']\n  },\n  series: [\n    {\n      name: 'Summary',\n      type: 'pie',\n      radius: '80%',\n      center: ['50%', '55%'],\n      itemStyle: {\n        normal: {color: getItemColor}\n      },\n      data: [\n        {value: 0, name: 'WA'},\n        {value: 0, name: 'AC'}\n      ],\n      label: {\n        normal: {\n          position: 'inner',\n          show: true,\n          formatter: '{b}: {c}\\n {d}%',\n          textStyle: {\n            fontWeight: 'bold'\n          }\n        }\n      }\n    }\n  ]\n}\n\nconst largePie = {\n  legend: {\n    left: 'center',\n    top:\n      '10',\n    orient:\n      'horizontal',\n    itemGap:\n      20,\n    data:\n      ['AC', 'RE', 'WA', 'TLE', 'PAC', 'MLE']\n  },\n  series: [\n    {\n      name: 'Detail',\n      type: 'pie',\n      radius: ['45%', '70%'],\n      center: ['50%', '55%'],\n      itemStyle: {\n        normal: {color: getItemColor}\n      },\n      data: [\n        {value: 0, name: 'RE'},\n        {value: 0, name: 'WA'},\n        {value: 0, name: 'TLE'},\n        {value: 0, name: 'AC'},\n        {value: 0, name: 'MLE'},\n        {value: 0, name: 'PAC'}\n      ],\n      label: {\n        normal: {\n          formatter: '{b}: {c}\\n {d}%'\n        }\n      },\n      labelLine: {\n        normal: {}\n      }\n    },\n    {\n      name: 'Summary',\n      type: 'pie',\n      radius: '30%',\n      center: ['50%', '55%'],\n      itemStyle: {\n        normal: {color: getItemColor}\n      },\n      data: [\n        {value: '0', name: 'WA'},\n        {value: 0, name: 'AC', selected: true}\n      ],\n      label: {\n        normal: {\n          position: 'inner',\n          formatter: '{b}: {c}\\n {d}%'\n        }\n      }\n    }\n  ]\n}\n\nexport { pie, largePie }\n"
  },
  {
    "path": "src/pages/oj/views/rank/ACMRank.vue",
    "content": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"22\">\n    <Panel :padding=\"10\">\n      <div slot=\"title\">{{$t('m.ACM_Ranklist')}}</div>\n      <div class=\"echarts\">\n        <ECharts :options=\"options\" ref=\"chart\" auto-resize></ECharts>\n      </div>\n    </Panel>\n    <Table :data=\"dataRank\" :columns=\"columns\" :loading=\"loadingTable\" size=\"large\"></Table>\n    <Pagination :total=\"total\" :page-size.sync=\"limit\" :current.sync=\"page\"\n                @on-change=\"getRankData\" show-sizer\n                @on-page-size-change=\"getRankData(1)\"></Pagination>\n    </Col>\n  </Row>\n</template>\n\n<script>\n  import api from '@oj/api'\n  import Pagination from '@oj/components/Pagination'\n  import utils from '@/utils/utils'\n  import { RULE_TYPE } from '@/utils/constants'\n\n  export default {\n    name: 'acm-rank',\n    components: {\n      Pagination\n    },\n    data () {\n      return {\n        page: 1,\n        limit: 30,\n        total: 0,\n        loadingTable: false,\n        dataRank: [],\n        columns: [\n          {\n            align: 'center',\n            width: 60,\n            render: (h, params) => {\n              return h('span', {}, params.index + (this.page - 1) * this.limit + 1)\n            }\n          },\n          {\n            title: this.$i18n.t('m.User_User'),\n            align: 'center',\n            render: (h, params) => {\n              return h('a', {\n                style: {\n                  'display': 'inline-block',\n                  'max-width': '200px'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push(\n                      {\n                        name: 'user-home',\n                        query: {username: params.row.user.username}\n                      })\n                  }\n                }\n              }, params.row.user.username)\n            }\n          },\n          {\n            title: this.$i18n.t('m.mood'),\n            align: 'center',\n            key: 'mood'\n          },\n          {\n            title: this.$i18n.t('m.AC'),\n            align: 'center',\n            key: 'accepted_number'\n          },\n          {\n            title: this.$i18n.t('m.Total'),\n            align: 'center',\n            key: 'submission_number'\n          },\n          {\n            title: this.$i18n.t('m.Rating'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span', utils.getACRate(params.row.accepted_number, params.row.submission_number))\n            }\n          }\n        ],\n        options: {\n          tooltip: {\n            trigger: 'axis'\n          },\n          legend: {\n            data: [this.$i18n.t('m.AC'), this.$i18n.t('m.Total')]\n          },\n          grid: {\n            x: '3%',\n            x2: '3%'\n          },\n          toolbox: {\n            show: true,\n            feature: {\n              dataView: {show: true, readOnly: true},\n              magicType: {show: true, type: ['line', 'bar', 'stack']},\n              saveAsImage: {show: true}\n            },\n            right: '10%'\n          },\n          calculable: true,\n          xAxis: [\n            {\n              type: 'category',\n              data: ['root'],\n              axisLabel: {\n                interval: 0,\n                showMinLabel: true,\n                showMaxLabel: true,\n                align: 'center',\n                formatter: (value, index) => {\n                  return utils.breakLongWords(value, 10)\n                }\n              }\n            }\n          ],\n          yAxis: [\n            {\n              type: 'value'\n            }\n          ],\n          series: [\n            {\n              name: this.$i18n.t('m.AC'),\n              type: 'bar',\n              data: [0],\n              markPoint: {\n                data: [\n                  {type: 'max', name: 'max'}\n                ]\n              }\n            },\n            {\n              name: this.$i18n.t('m.Total'),\n              type: 'bar',\n              data: [0],\n              markPoint: {\n                data: [\n                  {type: 'max', name: 'max'}\n                ]\n              }\n            }\n          ]\n        }\n      }\n    },\n    mounted () {\n      this.getRankData(1)\n    },\n    methods: {\n      getRankData (page) {\n        let offset = (page - 1) * this.limit\n        let bar = this.$refs.chart\n        bar.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'})\n        this.loadingTable = true\n        api.getUserRank(offset, this.limit, RULE_TYPE.ACM).then(res => {\n          this.loadingTable = false\n          if (page === 1) {\n            this.changeCharts(res.data.data.results.slice(0, 10))\n          }\n          this.total = res.data.data.total\n          this.dataRank = res.data.data.results\n          bar.hideLoading()\n        }).catch(() => {\n          this.loadingTable = false\n          bar.hideLoading()\n        })\n      },\n      changeCharts (rankData) {\n        let [usernames, acData, totalData] = [[], [], []]\n        rankData.forEach(ele => {\n          usernames.push(ele.user.username)\n          acData.push(ele.accepted_number)\n          totalData.push(ele.submission_number)\n        })\n        this.options.xAxis[0].data = usernames\n        this.options.series[0].data = acData\n        this.options.series[1].data = totalData\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .echarts {\n    margin: 0 auto;\n    width: 95%;\n    height: 400px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/rank/OIRank.vue",
    "content": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"22\">\n    <Panel :padding=\"10\">\n      <div slot=\"title\">{{$t('m.OI_Ranklist')}}</div>\n      <div class=\"echarts\">\n        <ECharts :options=\"options\" ref=\"chart\" auto-resize></ECharts>\n      </div>\n    </Panel>\n    <Table :data=\"dataRank\" :columns=\"columns\" size=\"large\"></Table>\n    <Pagination :total=\"total\" :page-size.sync=\"limit\" :current.sync=\"page\"\n                @on-change=\"getRankData\"\n                show-sizer @on-page-size-change=\"getRankData(1)\"></Pagination>\n    </Col>\n  </Row>\n</template>\n\n<script>\n  import api from '@oj/api'\n  import Pagination from '@oj/components/Pagination'\n  import utils from '@/utils/utils'\n  import { RULE_TYPE } from '@/utils/constants'\n\n  export default {\n    name: 'acm-rank',\n    components: {\n      Pagination\n    },\n    data () {\n      return {\n        page: 1,\n        limit: 30,\n        total: 0,\n        dataRank: [],\n        columns: [\n          {\n            align: 'center',\n            width: 60,\n            render: (h, params) => {\n              return h('span', {}, params.index + (this.page - 1) * this.limit + 1)\n            }\n          },\n          {\n            title: this.$i18n.t('m.User_User'),\n            align: 'center',\n            render: (h, params) => {\n              return h('a', {\n                style: {\n                  'display': 'inline-block',\n                  'max-width': '200px'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push(\n                      {\n                        name: 'user-home',\n                        query: {username: params.row.user.username}\n                      })\n                  }\n                }\n              }, params.row.user.username)\n            }\n          },\n          {\n            title: this.$i18n.t('m.mood'),\n            align: 'center',\n            key: 'mood'\n          },\n          {\n            title: this.$i18n.t('m.Score'),\n            align: 'center',\n            key: 'total_score'\n          },\n          {\n            title: this.$i18n.t('m.AC'),\n            align: 'center',\n            key: 'accepted_number'\n          },\n          {\n            title: this.$i18n.t('m.Total'),\n            align: 'center',\n            key: 'submission_number'\n          },\n          {\n            title: this.$i18n.t('m.Rating'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span', utils.getACRate(params.row.accepted_number, params.row.submission_number))\n            }\n          }\n        ],\n        options: {\n          tooltip: {\n            trigger: 'axis'\n          },\n          legend: {\n            data: [this.$i18n.t('m.Score')]\n          },\n          grid: {\n            x: '3%',\n            x2: '3%'\n          },\n          toolbox: {\n            show: true,\n            feature: {\n              dataView: {show: true, readOnly: true},\n              magicType: {show: true, type: ['line', 'bar']},\n              saveAsImage: {show: true}\n            },\n            right: '10%'\n          },\n          calculable: true,\n          xAxis: [\n            {\n              type: 'category',\n              data: ['root'],\n              boundaryGap: true,\n              axisLabel: {\n                interval: 0,\n                showMinLabel: true,\n                showMaxLabel: true,\n                align: 'center',\n                formatter: (value, index) => {\n                  return utils.breakLongWords(value, 14)\n                }\n              },\n              axisTick: {\n                alignWithLabel: true\n              }\n            }\n          ],\n          yAxis: [\n            {\n              type: 'value'\n            }\n          ],\n          series: [\n            {\n              name: this.$i18n.t('m.Score'),\n              type: 'bar',\n              data: [0],\n              barMaxWidth: '80',\n              markPoint: {\n                data: [\n                  {type: 'max', name: 'max'}\n                ]\n              }\n            }\n          ]\n        }\n      }\n    },\n    mounted () {\n      this.getRankData(1)\n    },\n    methods: {\n      getRankData (page) {\n        let offset = (page - 1) * this.limit\n        let bar = this.$refs.chart\n        bar.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'})\n        api.getUserRank(offset, this.limit, RULE_TYPE.OI).then(res => {\n          if (page === 1) {\n            this.changeCharts(res.data.data.results.slice(0, 10))\n          }\n          this.total = res.data.data.total\n          this.dataRank = res.data.data.results\n          bar.hideLoading()\n        })\n      },\n      changeCharts (rankData) {\n        let [usernames, scores] = [[], []]\n        rankData.forEach(ele => {\n          usernames.push(ele.user.username)\n          scores.push(ele.total_score)\n        })\n        this.options.xAxis[0].data = usernames\n        this.options.series[0].data = scores\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .echarts {\n    margin: 0 auto;\n    width: 95%;\n    height: 400px;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/setting/Settings.vue",
    "content": "<template>\n  <div class=\"container\">\n    <Card :padding=\"0\">\n      <div class=\"flex-container\">\n        <div class=\"menu\">\n          <Menu accordion @on-select=\"goRoute\" :activeName=\"activeName\" style=\"text-align: center;\" width=\"auto\">\n            <div class=\"avatar-editor\">\n              <div class=\"avatar-container\">\n                <img class=\"avatar\" :src=\"profile.avatar\"/>\n                <div class=\"avatar-mask\">\n                  <a @click.stop=\"goRoute({name: 'profile-setting'})\">\n                    <div class=\"mask-content\">\n                      <Icon type=\"camera\" size=\"30\"></Icon>\n                      <p class=\"text\">change avatar</p>\n                    </div>\n                  </a>\n                </div>\n              </div>\n            </div>\n\n            <Menu-item name=\"/setting/profile\">{{$t('m.Profile')}}</Menu-item>\n            <Menu-item name=\"/setting/account\">{{$t('m.Account')}}</Menu-item>\n            <Menu-item name=\"/setting/security\">{{$t('m.Security')}}</Menu-item>\n          </Menu>\n        </div>\n        <div class=\"panel\">\n          <transition name=\"fadeInUp\">\n            <router-view></router-view>\n          </transition>\n        </div>\n      </div>\n    </Card>\n  </div>\n</template>\n<script>\n  import { mapGetters } from 'vuex'\n\n  export default {\n    name: 'profile',\n    methods: {\n      goRoute (routePath) {\n        this.$router.push(routePath)\n      }\n    },\n    computed: {\n      ...mapGetters(['profile']),\n      activeName () {\n        return this.$route.path\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  @avatar-radius: 50%;\n\n  .container {\n    width: 90%;\n    min-width: 800px;\n    margin: auto;\n  }\n\n  .flex-container {\n    .menu {\n      flex: 1 0 150px;\n      max-width: 250px;\n      .avatar-editor {\n        padding: 10% 22%;\n        margin-bottom: 10px;\n        .avatar-container {\n          &:hover {\n            .avatar-mask {\n              opacity: .5;\n            }\n          }\n          position: relative;\n          .avatar {\n            width: 100%;\n            height: auto;\n            max-width: 100%;\n            display: block;\n            border-radius: @avatar-radius;\n            box-shadow: 0px 0px 1px 0px;\n          }\n          .avatar-mask {\n            transition: opacity .2s ease-in;\n            z-index: 1;\n            border-radius: @avatar-radius;\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: black;\n            opacity: 0;\n            .mask-content {\n              position: absolute;\n              top: 50%;\n              left: 50%;\n              z-index: 3;\n              color: #fff;\n              font-size: 16px;\n              text-align: center;\n              transform: translate(-50%, -50%);\n              .text {\n                white-space: nowrap;\n              }\n            }\n          }\n        }\n      }\n\n    }\n\n    .panel {\n      flex: auto;\n      &::before {\n        content: '';\n        display: block;\n        width: 1px;\n        height: 100%;\n        background: #dddee1;\n        position: absolute;\n        top: 0;\n        bottom: 0;\n        z-index: 1;\n      }\n    }\n\n  }\n\n  .ivu-menu-vertical.ivu-menu-light:after {\n    /*取消默认的伪元素*/\n    width: 0;\n  }\n</style>\n\n<style lang=\"less\">\n  .setting-main {\n    position: relative;\n    margin: 10px 40px;\n    padding-bottom: 20px;\n    .setting-content {\n      margin-left: 20px;\n    }\n    .mini-container {\n      width: 500px;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/setting/children/AccountSetting.vue",
    "content": "<template>\n  <div class=\"setting-main\">\n    <div class=\"flex-container\">\n      <div class=\"left\">\n        <p class=\"section-title\">{{$t('m.ChangePassword')}}</p>\n        <Form class=\"setting-content\" ref=\"formPassword\" :model=\"formPassword\" :rules=\"rulePassword\">\n          <FormItem label=\"Old Password\" prop=\"old_password\">\n            <Input v-model=\"formPassword.old_password\" type=\"password\"/>\n          </FormItem>\n          <FormItem label=\"New Password\" prop=\"new_password\">\n            <Input v-model=\"formPassword.new_password\" type=\"password\"/>\n          </FormItem>\n          <FormItem label=\"Confirm New Password\" prop=\"again_password\">\n            <Input v-model=\"formPassword.again_password\" type=\"password\"/>\n          </FormItem>\n          <FormItem v-if=\"visible.tfaRequired\" label=\"Two Factor Auth\" prop=\"tfa_code\">\n            <Input v-model=\"formPassword.tfa_code\"/>\n          </FormItem>\n          <FormItem v-if=\"visible.passwordAlert\">\n            <Alert type=\"success\">You will need to login again after 5 seconds..</Alert>\n          </FormItem>\n          <Button type=\"primary\" @click=\"changePassword\">{{$t('m.Update_Password')}}</Button>\n        </Form>\n      </div>\n\n      <div class=\"middle separator\"></div>\n\n      <div class=\"right\">\n        <p class=\"section-title\">{{$t('m.ChangeEmail')}}</p>\n        <Form class=\"setting-content\" ref=\"formEmail\" :model=\"formEmail\" :rules=\"ruleEmail\">\n          <FormItem label=\"Current Password\" prop=\"password\">\n            <Input v-model=\"formEmail.password\" type=\"password\"/>\n          </FormItem>\n          <FormItem label=\"Old Email\">\n            <Input v-model=\"formEmail.old_email\" disabled/>\n          </FormItem>\n          <FormItem label=\"New Email\" prop=\"new_email\">\n            <Input v-model=\"formEmail.new_email\"/>\n          </FormItem>\n          <FormItem v-if=\"visible.tfaRequired\" label=\"Two Factor Auth\" prop=\"tfa_code\">\n            <Input v-model=\"formEmail.tfa_code\"/>\n          </FormItem>\n          <Button type=\"primary\" @click=\"changeEmail\">{{$t('m.ChangeEmail')}}</Button>\n        </Form>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\n  import api from '@oj/api'\n  import { FormMixin } from '@oj/components/mixins'\n\n  export default {\n    mixins: [FormMixin],\n    data () {\n      const oldPasswordCheck = [{required: true, trigger: 'blur', min: 6, max: 20}]\n      const tfaCheck = [{required: true, trigger: 'change'}]\n      const CheckAgainPassword = (rule, value, callback) => {\n        if (value !== this.formPassword.new_password) {\n          callback(new Error('password does not match'))\n        }\n        callback()\n      }\n      const CheckNewPassword = (rule, value, callback) => {\n        if (this.formPassword.old_password !== '') {\n          if (this.formPassword.old_password === this.formPassword.new_password) {\n            callback(new Error('The new password doesn\\'t change'))\n          } else {\n            // 对第二个密码框再次验证\n            this.$refs.formPassword.validateField('again_password')\n          }\n        }\n        callback()\n      }\n      return {\n        loading: {\n          btnPassword: false,\n          btnEmail: false\n        },\n        visible: {\n          passwordAlert: false,\n          emailAlert: false,\n          tfaRequired: false\n        },\n        formPassword: {\n          tfa_code: '',\n          old_password: '',\n          new_password: '',\n          again_password: ''\n        },\n        formEmail: {\n          tfa_code: '',\n          password: '',\n          old_email: '',\n          new_email: ''\n        },\n        rulePassword: {\n          old_password: oldPasswordCheck,\n          new_password: [\n            {required: true, trigger: 'blur', min: 6, max: 20},\n            {validator: CheckNewPassword, trigger: 'blur'}\n          ],\n          again_password: [\n            {required: true, validator: CheckAgainPassword, trigger: 'change'}\n          ],\n          tfa_code: tfaCheck\n        },\n        ruleEmail: {\n          password: oldPasswordCheck,\n          new_email: [{required: true, type: 'email', trigger: 'change'}],\n          tfa_code: tfaCheck\n        }\n      }\n    },\n    mounted () {\n      this.formEmail.old_email = this.$store.getters.user.email || ''\n    },\n    methods: {\n      changePassword () {\n        this.validateForm('formPassword').then(valid => {\n          this.loading.btnPassword = true\n          let data = Object.assign({}, this.formPassword)\n          delete data.again_password\n          if (!this.visible.tfaRequired) {\n            delete data.tfa_code\n          }\n          api.changePassword(data).then(res => {\n            this.loading.btnPassword = false\n            this.visible.passwordAlert = true\n            this.$success('Update password successfully')\n            setTimeout(() => {\n              this.visible.passwordAlert = false\n              this.$router.push({name: 'logout'})\n            }, 5000)\n          }, res => {\n            if (res.data.data === 'tfa_required') {\n              this.visible.tfaRequired = true\n            }\n            this.loading.btnPassword = false\n          })\n        })\n      },\n      changeEmail () {\n        this.validateForm('formEmail').then(valid => {\n          this.loading.btnEmail = true\n          let data = Object.assign({}, this.formEmail)\n          if (!this.visible.tfaRequired) {\n            delete data.tfa_code\n          }\n          api.changeEmail(data).then(res => {\n            this.loading.btnEmail = false\n            this.visible.emailAlert = true\n            this.$success('Change email successfully')\n            this.$refs.formEmail.resetFields()\n          }, res => {\n            if (res.data.data === 'tfa_required') {\n              this.visible.tfaRequired = true\n            }\n          })\n        })\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n\n  .flex-container {\n    justify-content: flex-start;\n    .left {\n      flex: 1 0;\n      width: 250px;\n      padding-right: 5%;\n    }\n    > .middle {\n      flex: none;\n    }\n    .right {\n      flex: 1 0;\n      width: 250px;\n    }\n  }\n</style>\n\n"
  },
  {
    "path": "src/pages/oj/views/setting/children/ProfileSetting.vue",
    "content": "<template>\n  <div class=\"setting-main\">\n    <div class=\"section-title\">{{$t('m.Avatar_Setting')}}</div>\n    <template v-if=\"!avatarOption.imgSrc\">\n      <Upload type=\"drag\"\n              class=\"mini-container\"\n              accept=\".jpg,.jpeg,.png,.bmp,.gif\"\n              action=\"\"\n              :before-upload=\"handleSelectFile\">\n        <div style=\"padding: 30px 0\">\n          <Icon type=\"ios-cloud-upload\" size=\"52\" style=\"color: #3399ff\"></Icon>\n          <p>Drop here, or click to select manually</p>\n        </div>\n      </Upload>\n    </template>\n\n    <template v-else>\n      <div class=\"flex-container\">\n        <div class=\"cropper-main inline\">\n          <vueCropper\n            ref=\"cropper\"\n            autoCrop\n            fixed\n            :autoCropWidth=\"200\"\n            :autoCropHeight=\"200\"\n            :img=\"avatarOption.imgSrc\"\n            :outputSize=\"avatarOption.size\"\n            :outputType=\"avatarOption.outputType\"\n            :info=\"true\"\n            @realTime=\"realTime\">\n          </vueCropper>\n        </div>\n        <ButtonGroup vertical class=\"cropper-btn\">\n          <Button @click=\"rotate('left')\">\n            <Icon type=\"arrow-return-left\" size=\"20\"></Icon>\n          </Button>\n          <Button @click=\"rotate('right')\">\n            <Icon type=\"arrow-return-right\" size=\"20\"></Icon>\n          </Button>\n          <Button @click=\"reselect\">\n            <Icon type=\"refresh\" size=\"20\"></Icon>\n          </Button>\n          <Button @click=\"finishCrop\">\n            <Icon type=\"checkmark-round\" size=\"20\"></Icon>\n          </Button>\n        </ButtonGroup>\n        <div class=\"cropper-preview\" :style=\"previewStyle\">\n          <div :style=\" preview.div\">\n            <img :src=\"avatarOption.imgSrc\" :style=\"preview.img\">\n          </div>\n        </div>\n      </div>\n    </template>\n    <Modal v-model=\"uploadModalVisible\"\n           title=\"Upload the avatar\">\n      <div class=\"upload-modal\">\n        <p class=\"notice\">Your avatar will be set to:</p>\n        <img :src=\"uploadImgSrc\"/>\n      </div>\n      <div slot=\"footer\">\n        <Button @click=\"uploadAvatar\" :loading=\"loadingUploadBtn\">upload</Button>\n      </div>\n    </Modal>\n\n    <div class=\"section-title\">{{$t('m.Profile_Setting')}}</div>\n    <Form ref=\"formProfile\" :model=\"formProfile\">\n      <Row type=\"flex\" :gutter=\"30\" justify=\"space-around\">\n        <Col :span=\"11\">\n          <FormItem label=\"Real Name\">\n            <Input v-model=\"formProfile.real_name\"/>\n          </FormItem>\n          <Form-item label=\"School\">\n            <Input v-model=\"formProfile.school\"/>\n          </Form-item>\n          <Form-item label=\"Major\">\n            <Input v-model=\"formProfile.major\"/>\n          </Form-item>\n          <FormItem label=\"Language\">\n            <Select v-model=\"formProfile.language\">\n              <Option v-for=\"lang in languages\" :key=\"lang.value\" :value=\"lang.value\">{{lang.label}}</Option>\n            </Select>\n          </FormItem>\n          <Form-item>\n            <Button type=\"primary\" @click=\"updateProfile\" :loading=\"loadingSaveBtn\">Save All</Button>\n          </Form-item>\n        </Col>\n\n        <Col :span=\"11\">\n          <Form-item label=\"Mood\">\n            <Input v-model=\"formProfile.mood\"/>\n          </Form-item>\n          <Form-item label=\"Blog\">\n            <Input v-model=\"formProfile.blog\"/>\n          </Form-item>\n          <Form-item label=\"Github\">\n            <Input v-model=\"formProfile.github\"/>\n          </Form-item>\n        </Col>\n      </Row>\n    </Form>\n  </div>\n</template>\n\n<script>\n  import api from '@oj/api'\n  import utils from '@/utils/utils'\n  import {VueCropper} from 'vue-cropper'\n  import {types} from '@/store'\n  import {languages} from '@/i18n'\n\n  export default {\n    components: {\n      VueCropper\n    },\n    data () {\n      return {\n        loadingSaveBtn: false,\n        loadingUploadBtn: false,\n        uploadModalVisible: false,\n        preview: {},\n        uploadImgSrc: '',\n        avatarOption: {\n          imgSrc: '',\n          size: 0.8,\n          outputType: 'png'\n        },\n        languages: languages,\n        formProfile: {\n          real_name: '',\n          mood: '',\n          major: '',\n          blog: '',\n          school: '',\n          github: '',\n          language: ''\n        }\n      }\n    },\n    mounted () {\n      let profile = this.$store.state.user.profile\n      Object.keys(this.formProfile).forEach(element => {\n        if (profile[element] !== undefined) {\n          this.formProfile[element] = profile[element]\n        }\n      })\n    },\n    methods: {\n      checkFileType (file) {\n        if (!/\\.(gif|jpg|jpeg|png|bmp|GIF|JPG|PNG)$/.test(file.name)) {\n          this.$Notice.warning({\n            title: 'File type not support',\n            desc: 'The format of ' + file.name + ' is incorrect ，please choose image only.'\n          })\n          return false\n        }\n        return true\n      },\n      checkFileSize (file) {\n        // max size is 2MB\n        if (file.size > 2 * 1024 * 1024) {\n          this.$Notice.warning({\n            title: 'Exceed max size limit',\n            desc: 'File ' + file.name + ' is too big, you can upload a image up to 2MB in size'\n          })\n          return false\n        }\n        return true\n      },\n      handleSelectFile (file) {\n        let isOk = this.checkFileType(file) && this.checkFileSize(file)\n        if (!isOk) {\n          return false\n        }\n        let reader = new window.FileReader()\n        reader.onload = (e) => {\n          this.avatarOption.imgSrc = e.target.result\n        }\n        reader.readAsDataURL(file)\n        return false\n      },\n      realTime (data) {\n        this.preview = data\n      },\n      rotate (direction) {\n        if (direction === 'left') {\n          this.$refs.cropper.rotateLeft()\n        } else {\n          this.$refs.cropper.rotateRight()\n        }\n      },\n      reselect () {\n        this.$Modal.confirm({\n          content: 'Are you sure to disgard the changes?',\n          onOk: () => {\n            this.avatarOption.imgSrc = ''\n          }\n        })\n      },\n      finishCrop () {\n        this.$refs.cropper.getCropData(data => {\n          this.uploadImgSrc = data\n          this.uploadModalVisible = true\n        })\n      },\n      uploadAvatar () {\n        this.$refs.cropper.getCropBlob(blob => {\n          let form = new window.FormData()\n          let file = new window.File([blob], 'avatar.' + this.avatarOption.outputType)\n          form.append('image', file)\n          this.loadingUploadBtn = true\n          this.$http({\n            method: 'post',\n            url: 'upload_avatar',\n            data: form,\n            headers: {'content-type': 'multipart/form-data'}\n          }).then(res => {\n            this.loadingUploadBtn = false\n            this.$success('Successfully set new avatar')\n            this.uploadModalVisible = false\n            this.avatarOption.imgSrc = ''\n            this.$store.dispatch('getProfile')\n          }, () => {\n            this.loadingUploadBtn = false\n          })\n        })\n      },\n      updateProfile () {\n        this.loadingSaveBtn = true\n        let updateData = utils.filterEmptyValue(Object.assign({}, this.formProfile))\n        api.updateProfile(updateData).then(res => {\n          this.$success('Success')\n          this.$store.commit(types.CHANGE_PROFILE, {profile: res.data.data})\n          this.loadingSaveBtn = false\n        }, _ => {\n          this.loadingSaveBtn = false\n        })\n      }\n    },\n    computed: {\n      previewStyle () {\n        return {\n          'width': this.preview.w + 'px',\n          'height': this.preview.h + 'px',\n          'overflow': 'hidden'\n        }\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .inline {\n    display: inline-block;\n  }\n\n  .copper-img {\n    width: 400px;\n    height: 300px;\n  }\n\n  .flex-container {\n    flex-wrap: wrap;\n    justify-content: flex-start;\n    margin-bottom: 10px;\n    .cropper-main {\n      flex: none;\n      .copper-img;\n    }\n    .cropper-btn {\n      flex: none;\n      vertical-align: top;\n    }\n    .cropper-preview {\n      flex: none;\n      /*margin: 10px;*/\n      margin-left: 20px;\n      box-shadow: 0 0 1px 0;\n      .copper-img;\n    }\n  }\n\n  .upload-modal {\n    .notice {\n      font-size: 16px;\n      display: inline-block;\n      vertical-align: top;\n      padding: 10px;\n      padding-right: 15px;\n    }\n    img {\n      box-shadow: 0 0 1px 0;\n      border-radius: 50%;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/setting/children/SecuritySetting.vue",
    "content": "<template>\n  <div class=\"setting-main\">\n    <p class=\"section-title\">{{$t('m.Sessions')}}</p>\n    <div class=\"flex-container setting-content\">\n      <template v-for=\"session in sessions\">\n        <Card :padding=\"20\" class=\"flex-child\">\n          <span slot=\"title\" style=\"line-height: 20px\">{{session.ip}}</span>\n          <div slot=\"extra\">\n            <Tag v-if=\"session.current_session\" color=\"green\">Current</Tag>\n            <Button v-else\n                    type=\"warning\"\n                    size=\"small\"\n                    @click=\"deleteSession(session.session_key)\">Revoke\n            </Button>\n          </div>\n          <Form :label-width=\"100\">\n            <FormItem label=\"OS :\" class=\"item\">\n              {{session.user_agent | platform}}\n            </FormItem>\n            <FormItem label=\"Browser :\" class=\"item\">\n              {{session.user_agent | browser}}\n            </FormItem>\n            <FormItem label=\"Last Activity :\" class=\"item\">\n              {{session.last_activity | localtime }}\n            </FormItem>\n          </Form>\n        </Card>\n      </template>\n    </div>\n\n    <p class=\"section-title\">{{$t('m.Two_Factor_Authentication')}}</p>\n    <div class=\"mini-container setting-content\">\n      <Form>\n        <Alert v-if=\"TFAOpened\"\n               type=\"success\"\n               class=\"notice\"\n               showIcon>You have enabled two-factor authentication.\n        </Alert>\n        <FormItem v-if=\"!TFAOpened\">\n          <div class=\"oj-relative\">\n            <img :src=\"qrcodeSrc\" id=\"qr-img\">\n            <Spin size=\"large\" fix v-if=\"loadingQRcode\"></Spin>\n          </div>\n        </FormItem>\n        <template v-if=\"!loadingQRcode\">\n          <FormItem style=\"width: 250px\">\n            <Input v-model=\"formTwoFactor.code\" placeholder=\"Enter the code from your application\"/>\n          </FormItem>\n          <Button type=\"primary\"\n                  :loading=\"loadingBtn\"\n                  @click=\"updateTFA(false)\"\n                  v-if=\"!TFAOpened\">Open TFA\n          </Button>\n          <Button type=\"error\"\n                  :loading=\"loadingBtn\"\n                  @click=\"closeTFA\"\n                  v-else>Close TFA\n          </Button>\n        </template>\n      </Form>\n    </div>\n  </div>\n</template>\n\n<script>\n  import api from '@oj/api'\n  import {mapGetters, mapActions} from 'vuex'\n  import browserDetector from 'browser-detect'\n\n  const browsers = {}\n  const loadBrowser = (userAgent) => {\n    let browser = {}\n    if (userAgent in Object.keys(browsers)) {\n      browser = browsers[userAgent]\n    } else {\n      browser = browserDetector(userAgent)\n      browsers[userAgent] = browser\n    }\n    return browser\n  }\n\n  export default {\n    data () {\n      return {\n        qrcodeSrc: '',\n        loadingQRcode: false,\n        loadingBtn: false,\n        formTwoFactor: {\n          code: ''\n        },\n        sessions: []\n      }\n    },\n    mounted () {\n      this.getSessions()\n      if (!this.TFAOpened) {\n        this.getAuthImg()\n      }\n    },\n    methods: {\n      ...mapActions(['getProfile']),\n      getAuthImg () {\n        this.loadingQRcode = true\n        api.twoFactorAuth('get').then(res => {\n          this.loadingQRcode = false\n          this.qrcodeSrc = res.data.data\n        })\n      },\n      getSessions () {\n        api.getSessions().then(res => {\n          let data = res.data.data\n          // 将当前session放到第一个\n          let sessions = data.filter(session => {\n            return session.current_session\n          })\n          data.forEach(session => {\n            if (!session.current_session) {\n              sessions.push(session)\n            }\n          })\n          this.sessions = sessions\n        })\n      },\n      deleteSession (sessionKey) {\n        this.$Modal.confirm({\n          title: 'Confirm',\n          content: 'Are you sure to revoke the session?',\n          onOk: () => {\n            api.deleteSession(sessionKey).then(res => {\n              this.getSessions()\n            }, _ => {\n            })\n          }\n        })\n      },\n      closeTFA () {\n        this.$Modal.confirm({\n          title: 'Confirm',\n          content: 'Two-factor Authentication is a powerful tool to protect your account, are you sure to close it?',\n          onOk: () => {\n            this.updateTFA(true)\n          }\n        })\n      },\n      updateTFA (close) {\n        let method = close === false ? 'post' : 'put'\n        this.loadingBtn = true\n        api.twoFactorAuth(method, this.formTwoFactor).then(res => {\n          this.loadingBtn = false\n          this.getProfile()\n          if (close === true) {\n            this.getAuthImg()\n            this.formTwoFactor.code = ''\n          }\n          this.formTwoFactor.code = ''\n        }, err => {\n          this.formTwoFactor.code = ''\n          this.loadingBtn = false\n          if (err.data.data.indexOf('session') > -1) {\n            this.getProfile()\n            this.getAuthImg()\n          }\n        })\n      }\n    },\n    computed: {\n      ...mapGetters(['user']),\n      TFAOpened () {\n        return this.user && this.user.two_factor_auth\n      }\n    },\n    filters: {\n      browser (value) {\n        let b = loadBrowser(value)\n        if (b.name && b.version) {\n          return b.name + ' ' + b.version\n        } else {\n          return 'Unknown'\n        }\n      },\n      platform (value) {\n        let b = loadBrowser(value)\n        return b.os ? b.os : 'Unknown'\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .notice {\n    font-size: 16px;\n    margin-bottom: 20px;\n    display: inline-block;\n  }\n\n  .oj-relative {\n    width: 150px;\n    #qr-img {\n      width: 300px;\n      margin: -10px 0 -30px -20px;\n    }\n  }\n\n  .flex-container {\n    flex-flow: row wrap;\n    justify-content: flex-start;\n    .flex-child {\n      flex: 1 0;\n      max-width: 350px;\n      margin-right: 30px;\n      margin-bottom: 30px;\n      .item {\n        margin-bottom: 0;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/setting/index.js",
    "content": "const Settings = () => import(/* webpackChunkName: \"setting\" */ './Settings.vue')\nconst ProfileSetting = () => import(/* webpackChunkName: \"setting\" */ './children/ProfileSetting.vue')\nconst SecuritySetting = () => import(/* webpackChunkName: \"setting\" */ './children/SecuritySetting.vue')\nconst AccountSetting = () => import(/* webpackChunkName: \"setting\" */ './children/AccountSetting.vue')\n\nexport {Settings, ProfileSetting, SecuritySetting, AccountSetting}\n"
  },
  {
    "path": "src/pages/oj/views/submission/SubmissionDetails.vue",
    "content": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"20\" id=\"status\">\n      <Alert :type=\"status.type\" showIcon>\n        <span class=\"title\">{{$t('m.' + status.statusName.replace(/ /g, \"_\"))}}</span>\n        <div slot=\"desc\" class=\"content\">\n          <template v-if=\"isCE\">\n            <pre>{{submission.statistic_info.err_info}}</pre>\n          </template>\n          <template v-else>\n            <span>{{$t('m.Time')}}: {{submission.statistic_info.time_cost | submissionTime}}</span>\n            <span>{{$t('m.Memory')}}: {{submission.statistic_info.memory_cost | submissionMemory}}</span>\n            <span>{{$t('m.Lang')}}: {{submission.language}}</span>\n            <span>{{$t('m.Author')}}: {{submission.username}}</span>\n          </template>\n        </div>\n      </Alert>\n    </Col>\n\n    <!--后台返info就显示出来， 权限控制放后台 -->\n    <Col v-if=\"submission.info && !isCE\" :span=\"20\">\n      <Table stripe :loading=\"loading\" :disabled-hover=\"true\" :columns=\"columns\" :data=\"submission.info.data\"></Table>\n    </Col>\n\n    <Col :span=\"20\">\n      <Highlight :code=\"submission.code\" :language=\"submission.language\" :border-color=\"status.color\"></Highlight>\n    </Col>\n    <Col v-if=\"submission.can_unshare\" :span=\"20\">\n      <div id=\"share-btn\">\n        <Button v-if=\"submission.shared\"\n                type=\"warning\" size=\"large\" @click=\"shareSubmission(false)\">\n          {{$t('m.UnShare')}}\n        </Button>\n        <Button v-else\n                type=\"primary\" size=\"large\" @click=\"shareSubmission(true)\">\n          {{$t('m.Share')}}\n        </Button>\n      </div>\n    </Col>\n  </Row>\n\n</template>\n\n<script>\n  import api from '@oj/api'\n  import {JUDGE_STATUS} from '@/utils/constants'\n  import utils from '@/utils/utils'\n  import Highlight from '@/pages/oj/components/Highlight'\n\n  export default {\n    name: 'submissionDetails',\n    components: {\n      Highlight\n    },\n    data () {\n      return {\n        columns: [\n          {\n            title: this.$i18n.t('m.ID'),\n            align: 'center',\n            type: 'index'\n          },\n          {\n            title: this.$i18n.t('m.Status'),\n            align: 'center',\n            render: (h, params) => {\n              return h('Tag', {\n                props: {\n                  color: JUDGE_STATUS[params.row.result].color\n                }\n              }, this.$i18n.t('m.' + JUDGE_STATUS[params.row.result].name.replace(/ /g, '_')))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Memory'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span', utils.submissionMemoryFormat(params.row.memory))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Time'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span', utils.submissionTimeFormat(params.row.cpu_time))\n            }\n          }\n        ],\n        submission: {\n          result: '0',\n          code: '',\n          info: {\n            data: []\n          },\n          statistic_info: {\n            time_cost: '',\n            memory_cost: ''\n          }\n        },\n        isConcat: false,\n        loading: false\n      }\n    },\n    mounted () {\n      this.getSubmission()\n    },\n    methods: {\n      getSubmission () {\n        this.loading = true\n        api.getSubmission(this.$route.params.id).then(res => {\n          this.loading = false\n          let data = res.data.data\n          if (data.info && data.info.data && !this.isConcat) {\n            // score exist means the submission is OI problem submission\n            if (data.info.data[0].score !== undefined) {\n              this.isConcat = true\n              const scoreColumn = {\n                title: this.$i18n.t('m.Score'),\n                align: 'center',\n                key: 'score'\n              }\n              this.columns.push(scoreColumn)\n              this.loadingTable = false\n            }\n            if (this.isAdminRole) {\n              this.isConcat = true\n              const adminColumn = [\n                {\n                  title: this.$i18n.t('m.Real_Time'),\n                  align: 'center',\n                  render: (h, params) => {\n                    return h('span', utils.submissionTimeFormat(params.row.real_time))\n                  }\n                },\n                {\n                  title: this.$i18n.t('m.Signal'),\n                  align: 'center',\n                  key: 'signal'\n                }\n              ]\n              this.columns = this.columns.concat(adminColumn)\n            }\n          }\n          this.submission = data\n        }, () => {\n          this.loading = false\n        })\n      },\n      shareSubmission (shared) {\n        let data = {id: this.submission.id, shared: shared}\n        api.updateSubmission(data).then(res => {\n          this.getSubmission()\n          this.$success(this.$i18n.t('m.Succeeded'))\n        }, () => {\n        })\n      }\n    },\n    computed: {\n      status () {\n        return {\n          type: JUDGE_STATUS[this.submission.result].type,\n          statusName: JUDGE_STATUS[this.submission.result].name,\n          color: JUDGE_STATUS[this.submission.result].color\n        }\n      },\n      isCE () {\n        return this.submission.result === -2\n      },\n      isAdminRole () {\n        return this.$store.getters.isAdminRole\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  #status {\n    .title {\n      font-size: 20px;\n    }\n    .content {\n      margin-top: 10px;\n      font-size: 14px;\n      span {\n        margin-right: 10px;\n      }\n      pre {\n        white-space: pre-wrap;\n        word-wrap: break-word;\n        word-break: break-all;\n      }\n    }\n  }\n\n  .admin-info {\n    margin: 5px 0;\n    &-content {\n      font-size: 16px;\n      padding: 10px;\n    }\n  }\n\n  #share-btn {\n    float: right;\n    margin-top: 5px;\n    margin-right: 10px;\n  }\n\n  pre {\n    border: none;\n    background: none;\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/submission/SubmissionList.vue",
    "content": "<template>\n  <div class=\"flex-container\">\n    <div id=\"main\">\n      <Panel shadow>\n        <div slot=\"title\">{{title}}</div>\n        <div slot=\"extra\">\n          <ul class=\"filter\">\n            <li>\n              <Dropdown @on-click=\"handleResultChange\">\n                <span>{{status}}\n                  <Icon type=\"arrow-down-b\"></Icon>\n                </span>\n                <Dropdown-menu slot=\"list\">\n                  <Dropdown-item name=\"\">{{$t('m.All')}}</Dropdown-item>\n                  <Dropdown-item v-for=\"status in Object.keys(JUDGE_STATUS)\" :key=\"status\" :name=\"status\">\n                    {{$t('m.' + JUDGE_STATUS[status].name.replace(/ /g, \"_\"))}}\n                  </Dropdown-item>\n                </Dropdown-menu>\n              </Dropdown>\n            </li>\n\n\n            <li>\n              <i-switch size=\"large\" v-model=\"formFilter.myself\" @on-change=\"handleQueryChange\">\n                <span slot=\"open\">{{$t('m.Mine')}}</span>\n                <span slot=\"close\">{{$t('m.All')}}</span>\n              </i-switch>\n            </li>\n            <li>\n              <Input v-model=\"formFilter.username\" :placeholder=\"$t('m.Search_Author')\" @on-enter=\"handleQueryChange\"/>\n            </li>\n\n            <li>\n              <Button type=\"info\" icon=\"refresh\" @click=\"getSubmissions\">{{$t('m.Refresh')}}</Button>\n            </li>\n          </ul>\n        </div>\n        <Table stripe :disabled-hover=\"true\" :columns=\"columns\" :data=\"submissions\" :loading=\"loadingTable\"></Table>\n        <Pagination :total=\"total\" :page-size=\"limit\" @on-change=\"changeRoute\" :current.sync=\"page\"></Pagination>\n      </Panel>\n    </div>\n  </div>\n</template>\n\n<script>\n  import { mapGetters } from 'vuex'\n  import api from '@oj/api'\n  import { JUDGE_STATUS, USER_TYPE } from '@/utils/constants'\n  import utils from '@/utils/utils'\n  import time from '@/utils/time'\n  import Pagination from '@/pages/oj/components/Pagination'\n\n  export default {\n    name: 'submissionList',\n    components: {\n      Pagination\n    },\n    data () {\n      return {\n        formFilter: {\n          myself: false,\n          result: '',\n          username: ''\n        },\n        columns: [\n          {\n            title: this.$i18n.t('m.When'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span', time.utcToLocal(params.row.create_time))\n            }\n          },\n          {\n            title: this.$i18n.t('m.ID'),\n            align: 'center',\n            render: (h, params) => {\n              if (params.row.show_link) {\n                return h('span', {\n                  style: {\n                    color: '#57a3f3',\n                    cursor: 'pointer'\n                  },\n                  on: {\n                    click: () => {\n                      this.$router.push('/status/' + params.row.id)\n                    }\n                  }\n                }, params.row.id.slice(0, 12))\n              } else {\n                return h('span', params.row.id.slice(0, 12))\n              }\n            }\n          },\n          {\n            title: this.$i18n.t('m.Status'),\n            align: 'center',\n            render: (h, params) => {\n              return h('Tag', {\n                props: {\n                  color: JUDGE_STATUS[params.row.result].color\n                }\n              }, this.$i18n.t('m.' + JUDGE_STATUS[params.row.result].name.replace(/ /g, '_')))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Problem'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span',\n                {\n                  style: {\n                    color: '#57a3f3',\n                    cursor: 'pointer'\n                  },\n                  on: {\n                    click: () => {\n                      if (this.contestID) {\n                        this.$router.push(\n                          {\n                            name: 'contest-problem-details',\n                            params: {problemID: params.row.problem, contestID: this.contestID}\n                          })\n                      } else {\n                        this.$router.push({name: 'problem-details', params: {problemID: params.row.problem}})\n                      }\n                    }\n                  }\n                },\n                params.row.problem)\n            }\n          },\n          {\n            title: this.$i18n.t('m.Time'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span', utils.submissionTimeFormat(params.row.statistic_info.time_cost))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Memory'),\n            align: 'center',\n            render: (h, params) => {\n              return h('span', utils.submissionMemoryFormat(params.row.statistic_info.memory_cost))\n            }\n          },\n          {\n            title: this.$i18n.t('m.Language'),\n            align: 'center',\n            key: 'language'\n          },\n          {\n            title: this.$i18n.t('m.Author'),\n            align: 'center',\n            render: (h, params) => {\n              return h('a', {\n                style: {\n                  'display': 'inline-block',\n                  'max-width': '150px'\n                },\n                on: {\n                  click: () => {\n                    this.$router.push(\n                      {\n                        name: 'user-home',\n                        query: {username: params.row.username}\n                      })\n                  }\n                }\n              }, params.row.username)\n            }\n          }\n        ],\n        loadingTable: false,\n        submissions: [],\n        total: 30,\n        limit: 12,\n        page: 1,\n        contestID: '',\n        problemID: '',\n        routeName: '',\n        JUDGE_STATUS: '',\n        rejudge_column: false\n      }\n    },\n    mounted () {\n      this.init()\n      this.JUDGE_STATUS = Object.assign({}, JUDGE_STATUS)\n      // 去除submitting的状态 和 两个\n      delete this.JUDGE_STATUS['9']\n      delete this.JUDGE_STATUS['2']\n    },\n    methods: {\n      init () {\n        this.contestID = this.$route.params.contestID\n        let query = this.$route.query\n        this.problemID = query.problemID\n        this.formFilter.myself = query.myself === '1'\n        this.formFilter.result = query.result || ''\n        this.formFilter.username = query.username || ''\n        this.page = parseInt(query.page) || 1\n        if (this.page < 1) {\n          this.page = 1\n        }\n        this.routeName = this.$route.name\n        this.getSubmissions()\n      },\n      buildQuery () {\n        return {\n          myself: this.formFilter.myself === true ? '1' : '0',\n          result: this.formFilter.result,\n          username: this.formFilter.username,\n          page: this.page\n        }\n      },\n      getSubmissions () {\n        let params = this.buildQuery()\n        params.contest_id = this.contestID\n        params.problem_id = this.problemID\n        let offset = (this.page - 1) * this.limit\n        let func = this.contestID ? 'getContestSubmissionList' : 'getSubmissionList'\n        this.loadingTable = true\n        api[func](offset, this.limit, params).then(res => {\n          let data = res.data.data\n          for (let v of data.results) {\n            v.loading = false\n          }\n          this.adjustRejudgeColumn()\n          this.loadingTable = false\n          this.submissions = data.results\n          this.total = data.total\n        }).catch(() => {\n          this.loadingTable = false\n        })\n      },\n      // 改变route， 通过监听route变化请求数据，这样可以产生route history， 用户返回时就会保存之前的状态\n      changeRoute () {\n        let query = utils.filterEmptyValue(this.buildQuery())\n        query.contestID = this.contestID\n        query.problemID = this.problemID\n        let routeName = query.contestID ? 'contest-submission-list' : 'submission-list'\n        this.$router.push({\n          name: routeName,\n          query: utils.filterEmptyValue(query)\n        })\n      },\n      goRoute (route) {\n        this.$router.push(route)\n      },\n      adjustRejudgeColumn () {\n        if (!this.rejudgeColumnVisible || this.rejudge_column) {\n          return\n        }\n        const judgeColumn = {\n          title: this.$i18n.t('m.Option'),\n          fixed: 'right',\n          align: 'center',\n          width: 90,\n          render: (h, params) => {\n            return h('Button', {\n              props: {\n                type: 'primary',\n                size: 'small',\n                loading: params.row.loading\n              },\n              on: {\n                click: () => {\n                  this.handleRejudge(params.row.id, params.index)\n                }\n              }\n            }, this.$i18n.t('m.Rejudge'))\n          }\n        }\n        this.columns.push(judgeColumn)\n        this.rejudge_column = true\n      },\n      handleResultChange (status) {\n        this.page = 1\n        this.formFilter.result = status\n        this.changeRoute()\n      },\n      handleQueryChange () {\n        this.page = 1\n        this.changeRoute()\n      },\n      handleRejudge (id, index) {\n        this.submissions[index].loading = true\n        api.submissionRejudge(id).then(res => {\n          this.submissions[index].loading = false\n          this.$success('Succeeded')\n          this.getSubmissions()\n        }, () => {\n          this.submissions[index].loading = false\n        })\n      }\n    },\n    computed: {\n      ...mapGetters(['isAuthenticated', 'user']),\n      title () {\n        if (!this.contestID) {\n          return this.$i18n.t('m.Status')\n        } else if (this.problemID) {\n          return this.$i18n.t('m.Problem_Submissions')\n        } else {\n          return this.$i18n.t('m.Submissions')\n        }\n      },\n      status () {\n        return this.formFilter.result === '' ? this.$i18n.t('m.Status') : this.$i18n.t('m.' + JUDGE_STATUS[this.formFilter.result].name.replace(/ /g, '_'))\n      },\n      rejudgeColumnVisible () {\n        return !this.contestID && this.user.admin_type === USER_TYPE.SUPER_ADMIN\n      }\n    },\n    watch: {\n      '$route' (newVal, oldVal) {\n        if (newVal !== oldVal) {\n          this.init()\n        }\n      },\n      'rejudgeColumnVisible' () {\n        this.adjustRejudgeColumn()\n      },\n      'isAuthenticated' () {\n        this.init()\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .ivu-btn-text {\n    color: #57a3f3;\n  }\n\n  .flex-container {\n    #main {\n      flex: auto;\n      margin-right: 18px;\n      .filter {\n        margin-right: -10px;\n      }\n    }\n    #contest-menu {\n      flex: none;\n      width: 210px;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/user/ApplyResetPassword.vue",
    "content": "<template>\n  <Panel :padding=\"30\" class=\"container\">\n    <div slot=\"title\" class=\"center\">{{$t('m.Reset_Password')}}</div>\n    <template v-if=\"!successApply\">\n      <Form :rules=\"ruleResetPassword\" :model=formResetPassword ref=\"formResetPassword\">\n        <Form-item prop=\"email\">\n          <Input v-model=\"formResetPassword.email\" :placeholder=\"$t('m.ApplyEmail')\" size=\"large\">\n          <Icon type=\"ios-email-outline\" slot=\"prepend\"></Icon>\n          </Input>\n        </Form-item>\n        <Form-item prop=\"captcha\" style=\"margin-bottom:10px\">\n          <div class=\"oj-captcha\">\n            <div class=\"oj-captcha-code\">\n              <Input v-model=\"formResetPassword.captcha\" :placeholder=\"$t('m.RCaptcha')\" size=\"large\">\n              <Icon type=\"ios-lightbulb-outline\" slot=\"prepend\"></Icon>\n              </Input>\n            </div>\n            <div class=\"oj-captcha-img\">\n              <Tooltip content=\"Click to refresh\" placement=\"top\">\n                <img :src=\"captchaSrc\" @click=\"getCaptchaSrc\"/>\n              </Tooltip>\n            </div>\n          </div>\n        </Form-item>\n      </Form>\n      <Button type=\"primary\"\n              @click=\"sendEmail\"\n              class=\"btn\" long\n              :loading=\"btnLoading\">{{$t('m.Send_Password_Reset_Email')}}\n      </Button>\n    </template>\n    <template v-else>\n      <Alert type=\"success\" show-icon>\n        {{$t('Success')}}\n        <span slot=\"desc\"> {{$t('Password_reset_mail_sent')}}</span>\n      </Alert>\n    </template>\n  </Panel>\n</template>\n<script>\n  import api from '@oj/api'\n  import {FormMixin} from '@oj/components/mixins'\n\n  export default {\n    mixins: [FormMixin],\n    data () {\n      const CheckEmailExist = (rule, value, callback) => {\n        if (value !== '') {\n          api.checkUsernameOrEmail(undefined, value).then(res => {\n            if (res.data.data.email === false) {\n              callback(new Error(this.$i18n.t('m.The_email_doesnt_exist')))\n            } else {\n              callback()\n            }\n          }, _ => callback())\n        } else {\n          callback()\n        }\n      }\n      return {\n        captchaSrc: '',\n        successApply: false,\n        btnLoading: false,\n        formResetPassword: {\n          email: '',\n          captcha: ''\n        },\n        ruleResetPassword: {\n          email: [\n            {required: true, type: 'email', trigger: 'blur'},\n            {validator: CheckEmailExist, trigger: 'blur'}\n          ],\n          captcha: [\n            {required: true, trigger: 'blur', min: 1, max: 10}\n          ]\n        }\n      }\n    },\n    mounted () {\n      this.getCaptchaSrc()\n    },\n    methods: {\n      sendEmail () {\n        this.validateForm('formResetPassword').then(() => {\n          this.btnLoading = true\n          api.applyResetPassword(this.formResetPassword).then(res => {\n            // 伪加载\n            setTimeout(() => {\n              this.btnLoading = false\n              this.successApply = true\n            }, 2000)\n          }, _ => {\n            this.btnLoading = false\n            this.formResetPassword.captcha = ''\n            this.getCaptchaSrc()\n          })\n        })\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .container {\n    width: 450px;\n    margin: auto;\n    .center {\n      text-align: center;\n    }\n    .btn {\n      margin-top: 18px;\n      text-align: center;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/user/Login.vue",
    "content": "<template>\n  <div>\n    <Form ref=\"formLogin\" :model=\"formLogin\" :rules=\"ruleLogin\">\n      <FormItem prop=\"username\">\n        <Input type=\"text\" v-model=\"formLogin.username\" :placeholder=\"$t('m.LoginUsername')\" size=\"large\" @on-enter=\"handleLogin\">\n        <Icon type=\"ios-person-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </FormItem>\n      <FormItem prop=\"password\">\n        <Input type=\"password\" v-model=\"formLogin.password\" :placeholder=\"$t('m.LoginPassword')\" size=\"large\" @on-enter=\"handleLogin\">\n        <Icon type=\"ios-locked-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </FormItem>\n      <FormItem prop=\"tfa_code\" v-if=\"tfaRequired\">\n        <Input v-model=\"formLogin.tfa_code\" :placeholder=\"$t('m.TFA_Code')\">\n        <Icon type=\"ios-lightbulb-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </FormItem>\n    </Form>\n    <div class=\"footer\">\n      <Button\n        type=\"primary\"\n        @click=\"handleLogin\"\n        class=\"btn\" long\n        :loading=\"btnLoginLoading\">\n        {{$t('m.UserLogin')}}\n      </Button>\n      <a v-if=\"website.allow_register\" @click.stop=\"handleBtnClick('register')\">{{$t('m.No_Account')}}</a>\n      <a @click.stop=\"goResetPassword\" style=\"float: right\">{{$t('m.Forget_Password')}}</a>\n    </div>\n  </div>\n</template>\n\n<script>\n  import { mapGetters, mapActions } from 'vuex'\n  import api from '@oj/api'\n  import { FormMixin } from '@oj/components/mixins'\n\n  export default {\n    mixins: [FormMixin],\n    data () {\n      const CheckRequiredTFA = (rule, value, callback) => {\n        if (value !== '') {\n          api.tfaRequiredCheck(value).then(res => {\n            this.tfaRequired = res.data.data.result\n          })\n        }\n        callback()\n      }\n\n      return {\n        tfaRequired: false,\n        btnLoginLoading: false,\n        formLogin: {\n          username: '',\n          password: '',\n          tfa_code: ''\n        },\n        ruleLogin: {\n          username: [\n            {required: true, trigger: 'blur'},\n            {validator: CheckRequiredTFA, trigger: 'blur'}\n          ],\n          password: [\n            {required: true, trigger: 'change', min: 6, max: 20}\n          ]\n        }\n      }\n    },\n    methods: {\n      ...mapActions(['changeModalStatus', 'getProfile']),\n      handleBtnClick (mode) {\n        this.changeModalStatus({\n          mode,\n          visible: true\n        })\n      },\n      handleLogin () {\n        this.validateForm('formLogin').then(valid => {\n          this.btnLoginLoading = true\n          let formData = Object.assign({}, this.formLogin)\n          if (!this.tfaRequired) {\n            delete formData['tfa_code']\n          }\n          api.login(formData).then(res => {\n            this.btnLoginLoading = false\n            this.changeModalStatus({visible: false})\n            this.getProfile()\n            this.$success(this.$i18n.t('m.Welcome_back'))\n          }, _ => {\n            this.btnLoginLoading = false\n          })\n        })\n      },\n      goResetPassword () {\n        this.changeModalStatus({visible: false})\n        this.$router.push({name: 'apply-reset-password'})\n      }\n    },\n    computed: {\n      ...mapGetters(['website', 'modalStatus']),\n      visible: {\n        get () {\n          return this.modalStatus.visible\n        },\n        set (value) {\n          this.changeModalStatus({visible: value})\n        }\n      }\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .footer {\n    overflow: auto;\n    margin-top: 20px;\n    margin-bottom: -15px;\n    text-align: left;\n    .btn {\n      margin: 0 0 15px 0;\n      &:last-child {\n        margin: 0;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/user/Logout.vue",
    "content": "<template>\n</template>\n\n<script>\n  import api from '../../api.js'\n\n  export default {\n    mounted () {\n      api.logout().then(res => {\n        this.$store.dispatch('clearProfile')\n        this.$router.replace({\n          path: '/'\n        })\n      })\n    }\n  }\n</script>\n"
  },
  {
    "path": "src/pages/oj/views/user/Register.vue",
    "content": "<template>\n<div>\n    <Form ref=\"formRegister\" :model=\"formRegister\" :rules=\"ruleRegister\">\n      <FormItem prop=\"username\">\n        <Input type=\"text\" v-model=\"formRegister.username\" :placeholder=\"$t('m.RegisterUsername')\" size=\"large\" @on-enter=\"handleRegister\">\n        <Icon type=\"ios-person-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </FormItem>\n      <FormItem prop=\"email\">\n        <Input v-model=\"formRegister.email\" :placeholder=\"$t('m.Email_Address')\" size=\"large\" @on-enter=\"handleRegister\">\n        <Icon type=\"ios-email-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </FormItem>\n      <FormItem prop=\"password\">\n        <Input type=\"password\" v-model=\"formRegister.password\" :placeholder=\"$t('m.RegisterPassword')\" size=\"large\" @on-enter=\"handleRegister\">\n        <Icon type=\"ios-locked-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </FormItem>\n      <FormItem prop=\"passwordAgain\">\n        <Input type=\"password\" v-model=\"formRegister.passwordAgain\" :placeholder=\"$t('m.Password_Again')\" size=\"large\" @on-enter=\"handleRegister\">\n        <Icon type=\"ios-locked-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </FormItem>\n      <FormItem prop=\"captcha\" style=\"margin-bottom:10px\">\n        <div class=\"oj-captcha\">\n          <div class=\"oj-captcha-code\">\n            <Input v-model=\"formRegister.captcha\" :placeholder=\"$t('m.Captcha')\" size=\"large\" @on-enter=\"handleRegister\">\n            <Icon type=\"ios-lightbulb-outline\" slot=\"prepend\"></Icon>\n            </Input>\n          </div>\n          <div class=\"oj-captcha-img\">\n            <Tooltip content=\"Click to refresh\" placement=\"top\">\n              <img :src=\"captchaSrc\" @click=\"getCaptchaSrc\"/>\n            </Tooltip>\n          </div>\n        </div>\n      </FormItem>\n    </Form>\n    <div class=\"footer\">\n      <Button\n        type=\"primary\"\n        @click=\"handleRegister\"\n        class=\"btn\" long\n        :loading=\"btnRegisterLoading\">\n        {{$t('m.UserRegister')}}\n      </Button>\n      <Button\n        type=\"ghost\"\n        @click=\"switchMode('login')\"\n        class=\"btn\" long>\n        {{$t('m.Already_Registed')}}\n      </Button>\n    </div>\n  </div>\n</template>\n\n<script>\n  import { mapGetters, mapActions } from 'vuex'\n  import api from '@oj/api'\n  import { FormMixin } from '@oj/components/mixins'\n\n  export default {\n    mixins: [FormMixin],\n    mounted () {\n      this.getCaptchaSrc()\n    },\n    data () {\n      const CheckUsernameNotExist = (rule, value, callback) => {\n        api.checkUsernameOrEmail(value, undefined).then(res => {\n          if (res.data.data.username === true) {\n            callback(new Error(this.$i18n.t('m.The_username_already_exists')))\n          } else {\n            callback()\n          }\n        }, _ => callback())\n      }\n      const CheckEmailNotExist = (rule, value, callback) => {\n        api.checkUsernameOrEmail(undefined, value).then(res => {\n          if (res.data.data.email === true) {\n            callback(new Error(this.$i18n.t('m.The_email_already_exists')))\n          } else {\n            callback()\n          }\n        }, _ => callback())\n      }\n      const CheckPassword = (rule, value, callback) => {\n        if (this.formRegister.password !== '') {\n          // 对第二个密码框再次验证\n          this.$refs.formRegister.validateField('passwordAgain')\n        }\n        callback()\n      }\n\n      const CheckAgainPassword = (rule, value, callback) => {\n        if (value !== this.formRegister.password) {\n          callback(new Error(this.$i18n.t('m.password_does_not_match')))\n        }\n        callback()\n      }\n\n      return {\n        btnRegisterLoading: false,\n        formRegister: {\n          username: '',\n          password: '',\n          passwordAgain: '',\n          email: '',\n          captcha: ''\n        },\n        ruleRegister: {\n          username: [\n            {required: true, trigger: 'blur'},\n            {validator: CheckUsernameNotExist, trigger: 'blur'}\n          ],\n          email: [\n            {required: true, type: 'email', trigger: 'blur'},\n            {validator: CheckEmailNotExist, trigger: 'blur'}\n          ],\n          password: [\n            {required: true, trigger: 'blur', min: 6, max: 20},\n            {validator: CheckPassword, trigger: 'blur'}\n          ],\n          passwordAgain: [\n            {required: true, validator: CheckAgainPassword, trigger: 'change'}\n          ],\n          captcha: [\n            {required: true, trigger: 'blur', min: 1, max: 10}\n          ]\n        }\n      }\n    },\n    methods: {\n      ...mapActions(['changeModalStatus', 'getProfile']),\n      switchMode (mode) {\n        this.changeModalStatus({\n          mode,\n          visible: true\n        })\n      },\n      handleRegister () {\n        this.validateForm('formRegister').then(valid => {\n          let formData = Object.assign({}, this.formRegister)\n          delete formData['passwordAgain']\n          this.btnRegisterLoading = true\n          api.register(formData).then(res => {\n            this.$success(this.$i18n.t('m.Thanks_for_registering'))\n            this.switchMode('login')\n            this.btnRegisterLoading = false\n          }, _ => {\n            this.getCaptchaSrc()\n            this.formRegister.captcha = ''\n            this.btnRegisterLoading = false\n          })\n        })\n      }\n    },\n    computed: {\n      ...mapGetters(['website', 'modalStatus'])\n\n    }\n  }\n</script>\n\n<style scoped lang=\"less\">\n  .footer {\n    overflow: auto;\n    margin-top: 20px;\n    margin-bottom: -15px;\n    text-align: left;\n    .btn {\n      margin: 0 0 15px 0;\n      &:last-child {\n        margin: 0;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/user/ResetPassword.vue",
    "content": "<template>\n  <Panel :padding=\"30\" class=\"container\">\n    <div slot=\"title\" class=\"center\">{{$t('m.Reset_Password')}}</div>\n    <template v-if=\"!resetSuccess\">\n    <Form :model=formResetPassword ref=\"formResetPassword\" :rules=\"ruleResetPassword\">\n      <Form-item prop=\"password\">\n        <Input type=\"password\" v-model=\"formResetPassword.password\" :placeholder=\"$t('m.RPassword')\" size=\"large\">\n        <Icon type=\"ios-locked-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </Form-item>\n      <Form-item prop=\"passwordAgain\">\n        <Input type=\"password\" v-model=\"formResetPassword.passwordAgain\" :placeholder=\"$t('m.RPassword_Again')\" size=\"large\">\n        <Icon type=\"ios-locked-outline\" slot=\"prepend\"></Icon>\n        </Input>\n      </Form-item>\n      <Form-item prop=\"captcha\" style=\"margin-bottom:10px\">\n        <div id=\"captcha\">\n          <div id=\"captchaCode\">\n            <Input v-model=\"formResetPassword.captcha\" :placeholder=\"$t('m.RCaptcha')\" size=\"large\">\n            <Icon type=\"ios-lightbulb-outline\" slot=\"prepend\"></Icon>\n            </Input>\n          </div>\n          <div id=\"captchaImg\">\n            <Tooltip content=\"Click to refresh\" placement=\"top\">\n              <img :src=\"captchaSrc\" @click=\"getCaptchaSrc\"/>\n            </Tooltip>\n          </div>\n        </div>\n      </Form-item>\n    </Form>\n    <Button type=\"primary\"\n            @click=\"resetPassword\"\n            class=\"btn\" long\n            :loading=\"btnLoading\">{{$t('m.Reset_Password')}}\n    </Button>\n    </template>\n\n    <template v-else>\n      <Alert type=\"success\">{{$t('m.Your_password_has_been_reset')}}</Alert>\n    </template>\n  </Panel>\n</template>\n\n<script>\n  import {FormMixin} from '@oj/components/mixins'\n  import api from '@oj/api'\n\n  export default {\n    name: 'reset-password',\n    mixins: [FormMixin],\n    data () {\n      const CheckPassword = (rule, value, callback) => {\n        if (this.formResetPassword.passwdCheck !== '') {\n          // 对第二个密码框再次验证\n          this.$refs.formResetPassword.validateField('passwordAgain')\n        }\n        callback()\n      }\n\n      const CheckAgainPassword = (rule, value, callback) => {\n        if (value !== this.formResetPassword.password) {\n          callback(new Error(this.$i18n.t('m.password_does_not_match')))\n        }\n        callback()\n      }\n      return {\n        btnLoading: false,\n        captchaSrc: '',\n        resetSuccess: false,\n        formResetPassword: {\n          captcha: '',\n          password: '',\n          passwordAgain: '',\n          token: ''\n        },\n        ruleResetPassword: {\n          password: [\n            {required: true, trigger: 'blur', min: 6, max: 20},\n            {validator: CheckPassword, trigger: 'blur'}\n          ],\n          passwordAgain: [\n            {required: true, validator: CheckAgainPassword, trigger: 'change'}\n          ],\n          captcha: [\n            {required: true, trigger: 'blur', min: 1, max: 10}\n          ]\n        }\n      }\n    },\n    mounted () {\n      this.formResetPassword.token = this.$route.params.token\n      this.getCaptchaSrc()\n    },\n    methods: {\n      resetPassword () {\n        this.validateForm('formResetPassword').then(valid => {\n          this.btnLoading = true\n          let data = Object.assign({}, this.formResetPassword)\n          delete data.passwordAgain\n          api.resetPassword(data).then(res => {\n            this.btnLoading = false\n            this.resetSuccess = true\n          }, _ => {\n            this.btnLoading = false\n            this.formResetPassword.captcha = ''\n            this.getCaptchaSrc()\n          })\n        })\n      }\n    }\n  }\n</script>\n<style lang=\"less\" scoped>\n  .container {\n    width: 450px;\n    margin: auto;\n    .center {\n      text-align: center;\n    }\n    #captcha {\n      display: flex;\n      flex-wrap: nowrap;\n      justify-content: space-between;\n      width: 100%;\n      height: 36px;\n      #captchaCode {\n        flex: auto;\n      }\n      #captchaImg {\n        margin-left: 10px;\n        padding: 3px;\n        flex: initial;\n      }\n    }\n    .btn {\n      margin-top: 18px;\n      text-align: center;\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/pages/oj/views/user/UserHome.vue",
    "content": "<template>\n  <div class=\"container\">\n    <div class=\"avatar-container\">\n      <img class=\"avatar\" :src=\"profile.avatar\"/>\n    </div>\n    <Card :padding=\"100\">\n      <div v-if=\"profile.user\">\n        <p style=\"margin-top: -10px\">\n          <span v-if=\"profile.user\" class=\"emphasis\">{{profile.user.username}}</span>\n          <span v-if=\"profile.school\">@{{profile.school}}</span>\n        </p>\n        <p v-if=\"profile.mood\">\n          {{profile.mood}}\n        </p>\n        <hr id=\"split\"/>\n\n        <div class=\"flex-container\">\n          <div class=\"left\">\n            <p>{{$t('m.UserHomeSolved')}}</p>\n            <p class=\"emphasis\">{{profile.accepted_number}}</p>\n          </div>\n          <div class=\"middle\">\n            <p>{{$t('m.UserHomeserSubmissions')}}</p>\n            <p class=\"emphasis\">{{profile.submission_number}}</p>\n          </div>\n          <div class=\"right\">\n            <p>{{$t('m.UserHomeScore')}}</p>\n            <p class=\"emphasis\">{{profile.total_score}}</p>\n          </div>\n        </div>\n        <div id=\"problems\">\n          <div v-if=\"problems.length\">{{$t('m.List_Solved_Problems')}}\n            <Poptip v-if=\"refreshVisible\" trigger=\"hover\" placement=\"right-start\">\n              <Icon type=\"ios-help-outline\"></Icon>\n              <div slot=\"content\">\n                <p>If you find the following problem id does not exist,<br> try to click the button.</p>\n                <Button type=\"info\" @click=\"freshProblemDisplayID\">regenerate</Button>\n              </div>\n            </Poptip>\n          </div>\n          <p v-else>{{$t('m.UserHomeIntro')}}</p>\n          <div class=\"btns\">\n            <div class=\"problem-btn\" v-for=\"problemID of problems\" :key=\"problemID\">\n              <Button type=\"ghost\" @click=\"goProblem(problemID)\">{{problemID}}</Button>\n            </div>\n          </div>\n        </div>\n        <div id=\"icons\">\n          <a :href=\"profile.github\">\n            <Icon type=\"social-github-outline\" size=\"30\"></Icon>\n          </a>\n          <a :href=\"'mailto:'+ profile.user.email\">\n            <Icon class=\"icon\" type=\"ios-email-outline\" size=\"30\"></Icon>\n          </a>\n          <a :href=\"profile.blog\">\n            <Icon class=\"icon\" type=\"ios-world-outline\" size=\"30\"></Icon>\n          </a>\n        </div>\n      </div>\n    </Card>\n  </div>\n</template>\n<script>\n  import { mapActions } from 'vuex'\n  import time from '@/utils/time'\n  import api from '@oj/api'\n\n  export default {\n    data () {\n      return {\n        username: '',\n        profile: {},\n        problems: []\n      }\n    },\n    mounted () {\n      this.init()\n    },\n    methods: {\n      ...mapActions(['changeDomTitle']),\n      init () {\n        this.username = this.$route.query.username\n        api.getUserInfo(this.username).then(res => {\n          this.changeDomTitle({title: res.data.data.user.username})\n          this.profile = res.data.data\n          this.getSolvedProblems()\n          let registerTime = time.utcToLocal(this.profile.user.create_time, 'YYYY-MM-D')\n          console.log('The guy registered at ' + registerTime + '.')\n        })\n      },\n      getSolvedProblems () {\n        let ACMProblems = this.profile.acm_problems_status.problems || {}\n        let OIProblems = this.profile.oi_problems_status.problems || {}\n        // todo oi problems\n        let ACProblems = []\n        for (let problems of [ACMProblems, OIProblems]) {\n          Object.keys(problems).forEach(problemID => {\n            if (problems[problemID]['status'] === 0) {\n              ACProblems.push(problems[problemID]['_id'])\n            }\n          })\n        }\n        ACProblems.sort()\n        this.problems = ACProblems\n      },\n      goProblem (problemID) {\n        this.$router.push({name: 'problem-details', params: {problemID: problemID}})\n      },\n      freshProblemDisplayID () {\n        api.freshDisplayID().then(res => {\n          this.$success('Update successfully')\n          this.init()\n        })\n      }\n    },\n    computed: {\n      refreshVisible () {\n        if (!this.username) return true\n        if (this.username && this.username === this.$store.getters.user.username) return true\n        return false\n      }\n    },\n    watch: {\n      '$route' (newVal, oldVal) {\n        if (newVal !== oldVal) {\n          this.init()\n        }\n      }\n    }\n  }\n</script>\n\n<style lang=\"less\" scoped>\n  .container {\n    position: relative;\n    width: 75%;\n    margin: 170px auto;\n    text-align: center;\n    p {\n      margin-top: 8px;\n      margin-bottom: 8px;\n    }\n    .avatar-container {\n      position: absolute;\n      left: 50%;\n      transform: translate(-50%);\n      z-index: 1;\n      top: -90px;\n      .avatar {\n        width: 140px;\n        height: 140px;\n        border-radius: 50%;\n        box-shadow: 0 1px 1px 0;\n      }\n    }\n    .emphasis {\n      font-size: 20px;\n      font-weight: 600;\n    }\n    #split {\n      margin: 20px auto;\n      width: 90%;\n    }\n    .flex-container {\n      margin-top: 30px;\n      padding: auto 20px;\n      .left {\n        flex: 1 1;\n      }\n      .middle {\n        flex: 1 1;\n        border-left: 1px solid #999;\n        border-right: 1px solid #999;\n      }\n      .right {\n        flex: 1 1;\n      }\n    }\n    #problems {\n      margin-top: 40px;\n      padding-left: 30px;\n      padding-right: 30px;\n      font-size: 18px;\n      .btns {\n        margin-top: 15px;\n        .problem-btn {\n          display: inline-block;\n          margin: 5px;\n        }\n      }\n    }\n    #icons {\n      position: absolute;\n      bottom: 20px;\n      left: 50%;\n      transform: translate(-50%);\n      .icon {\n        padding-left: 20px;\n      }\n    }\n  }\n</style>\n"
  },
  {
    "path": "src/plugins/highlight.js",
    "content": "import hljs from 'highlight.js/lib/highlight'\nimport cpp from 'highlight.js/lib/languages/cpp'\nimport python from 'highlight.js/lib/languages/python'\nimport java from 'highlight.js/lib/languages/java'\nimport 'highlight.js/styles/atom-one-light.css'\n\nhljs.registerLanguage('cpp', cpp)\nhljs.registerLanguage('java', java)\nhljs.registerLanguage('python', python)\n\nexport default {\n  install (Vue, options) {\n    Vue.directive('highlight', {\n      deep: true,\n      bind: function (el, binding) {\n        // on first bind, highlight all targets\n        Array.from(el.querySelectorAll('code')).forEach((target) => {\n          // if a value is directly assigned to the directive, use this\n          // instead of the element content.\n          if (binding.value) {\n            target.textContent = binding.value\n          }\n          hljs.highlightBlock(target)\n        })\n      },\n      componentUpdated: function (el, binding) {\n        // after an update, re-fill the content and then highlight\n        Array.from(el.querySelectorAll('code')).forEach((target) => {\n          if (binding.value) {\n            target.textContent = binding.value\n          }\n          hljs.highlightBlock(target)\n        })\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "src/plugins/katex.js",
    "content": "import 'katex'\nimport renderMathInElement from 'katex/contrib/auto-render/auto-render'\nimport 'katex/dist/katex.min.css'\n\nfunction _ () {\n}\n\nconst defaultOptions = {\n  errorCallback: _,\n  throwOnError: false,\n  delimiters: [\n    {left: '$$', right: '$$', display: true},\n    {left: '$', right: '$', display: false},\n    {left: '\\\\[', right: '\\\\]', display: true},\n    {left: '\\\\(', right: '\\\\)', display: false}\n  ]\n}\n\nfunction render (el, binding) {\n  let options = {}\n  if (binding.value) {\n    options = binding.value.options || {}\n  }\n  Object.assign(options, defaultOptions)\n  renderMathInElement(el, options)\n}\n\nexport default {\n  install: function (Vue, options) {\n    Vue.directive('katex', {\n      bind: render,\n      componentUpdated: render\n    })\n  }\n}\n"
  },
  {
    "path": "src/store/index.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport user from './modules/user'\nimport contest from './modules/contest'\nimport api from '@oj/api'\nimport types from './types'\n\nVue.use(Vuex)\nconst debug = process.env.NODE_ENV !== 'production'\n\nconst rootState = {\n  website: {},\n  modalStatus: {\n    mode: 'login', // or 'register',\n    visible: false\n  }\n}\n\nconst rootGetters = {\n  'website' (state) {\n    return state.website\n  },\n  'modalStatus' (state) {\n    return state.modalStatus\n  }\n}\n\nconst rootMutations = {\n  [types.UPDATE_WEBSITE_CONF] (state, payload) {\n    state.website = payload.websiteConfig\n  },\n  [types.CHANGE_MODAL_STATUS] (state, {mode, visible}) {\n    if (mode !== undefined) {\n      state.modalStatus.mode = mode\n    }\n    if (visible !== undefined) {\n      state.modalStatus.visible = visible\n    }\n  }\n}\n\nconst rootActions = {\n  getWebsiteConfig ({commit}) {\n    api.getWebsiteConf().then(res => {\n      commit(types.UPDATE_WEBSITE_CONF, {\n        websiteConfig: res.data.data\n      })\n    })\n  },\n  changeModalStatus ({commit}, payload) {\n    commit(types.CHANGE_MODAL_STATUS, payload)\n  },\n  changeDomTitle ({commit, state}, payload) {\n    if (payload && payload.title) {\n      window.document.title = state.website.website_name_shortcut + ' | ' + payload.title\n    } else {\n      window.document.title = state.website.website_name_shortcut + ' | ' + state.route.meta.title\n    }\n  }\n}\n\nexport default new Vuex.Store({\n  modules: {\n    user,\n    contest\n  },\n  state: rootState,\n  getters: rootGetters,\n  mutations: rootMutations,\n  actions: rootActions,\n  strict: debug\n})\n\nexport { types }\n"
  },
  {
    "path": "src/store/modules/contest.js",
    "content": "import moment from 'moment'\nimport types from '../types'\nimport api from '@oj/api'\nimport { CONTEST_STATUS, USER_TYPE, CONTEST_TYPE } from '@/utils/constants'\n\nconst state = {\n  now: moment(),\n  access: false,\n  rankLimit: 30,\n  forceUpdate: false,\n  contest: {\n    created_by: {},\n    contest_type: CONTEST_TYPE.PUBLIC\n  },\n  contestProblems: [],\n  itemVisible: {\n    menu: true,\n    chart: true,\n    realName: false\n  }\n}\n\nconst getters = {\n  // contest 是否加载完成\n  contestLoaded: (state) => {\n    return !!state.contest.status\n  },\n  contestStatus: (state, getters) => {\n    if (!getters.contestLoaded) return null\n    let startTime = moment(state.contest.start_time)\n    let endTime = moment(state.contest.end_time)\n    let now = state.now\n\n    if (startTime > now) {\n      return CONTEST_STATUS.NOT_START\n    } else if (endTime < now) {\n      return CONTEST_STATUS.ENDED\n    } else {\n      return CONTEST_STATUS.UNDERWAY\n    }\n  },\n  contestRuleType: (state) => {\n    return state.contest.rule_type || null\n  },\n  isContestAdmin: (state, getters, _, rootGetters) => {\n    return rootGetters.isAuthenticated &&\n      (state.contest.created_by.id === rootGetters.user.id || rootGetters.user.admin_type === USER_TYPE.SUPER_ADMIN)\n  },\n  contestMenuDisabled: (state, getters) => {\n    if (getters.isContestAdmin) return false\n    if (state.contest.contest_type === CONTEST_TYPE.PUBLIC) {\n      return getters.contestStatus === CONTEST_STATUS.NOT_START\n    }\n    return !state.access\n  },\n  OIContestRealTimePermission: (state, getters, _, rootGetters) => {\n    if (getters.contestRuleType === 'ACM' || getters.contestStatus === CONTEST_STATUS.ENDED) {\n      return true\n    }\n    return state.contest.real_time_rank === true || getters.isContestAdmin\n  },\n  problemSubmitDisabled: (state, getters, _, rootGetters) => {\n    if (getters.contestStatus === CONTEST_STATUS.ENDED) {\n      return true\n    } else if (getters.contestStatus === CONTEST_STATUS.NOT_START) {\n      return !getters.isContestAdmin\n    }\n    return !rootGetters.isAuthenticated\n  },\n  passwordFormVisible: (state, getters) => {\n    return state.contest.contest_type !== CONTEST_TYPE.PUBLIC && !state.access && !getters.isContestAdmin\n  },\n  contestStartTime: (state) => {\n    return moment(state.contest.start_time)\n  },\n  contestEndTime: (state) => {\n    return moment(state.contest.end_time)\n  },\n  countdown: (state, getters) => {\n    if (getters.contestStatus === CONTEST_STATUS.NOT_START) {\n      let duration = moment.duration(getters.contestStartTime.diff(state.now, 'seconds'), 'seconds')\n      // time is too long\n      if (duration.weeks() > 0) {\n        return 'Start At ' + duration.humanize()\n      }\n      let texts = [Math.floor(duration.asHours()), duration.minutes(), duration.seconds()]\n      return '-' + texts.join(':')\n    } else if (getters.contestStatus === CONTEST_STATUS.UNDERWAY) {\n      let duration = moment.duration(getters.contestEndTime.diff(state.now, 'seconds'), 'seconds')\n      let texts = [Math.floor(duration.asHours()), duration.minutes(), duration.seconds()]\n      return '-' + texts.join(':')\n    } else {\n      return 'Ended'\n    }\n  }\n}\n\nconst mutations = {\n  [types.CHANGE_CONTEST] (state, payload) {\n    state.contest = payload.contest\n  },\n  [types.CHANGE_CONTEST_ITEM_VISIBLE] (state, payload) {\n    state.itemVisible = {...state.itemVisible, ...payload}\n  },\n  [types.CHANGE_RANK_FORCE_UPDATE] (state, payload) {\n    state.forceUpdate = payload.value\n  },\n  [types.CHANGE_CONTEST_PROBLEMS] (state, payload) {\n    state.contestProblems = payload.contestProblems\n  },\n  [types.CHANGE_CONTEST_RANK_LIMIT] (state, payload) {\n    state.rankLimit = payload.rankLimit\n  },\n  [types.CONTEST_ACCESS] (state, payload) {\n    state.access = payload.access\n  },\n  [types.CLEAR_CONTEST] (state) {\n    state.contest = {created_by: {}}\n    state.contestProblems = []\n    state.access = false\n    state.itemVisible = {\n      menu: true,\n      chart: true,\n      realName: false\n    }\n    state.forceUpdate = false\n  },\n  [types.NOW] (state, payload) {\n    state.now = payload.now\n  },\n  [types.NOW_ADD_1S] (state) {\n    state.now = moment(state.now.add(1, 's'))\n  }\n}\n\nconst actions = {\n  getContest ({commit, rootState, dispatch}) {\n    return new Promise((resolve, reject) => {\n      api.getContest(rootState.route.params.contestID).then((res) => {\n        resolve(res)\n        let contest = res.data.data\n        commit(types.CHANGE_CONTEST, {contest: contest})\n        commit(types.NOW, {now: moment(contest.now)})\n        if (contest.contest_type === CONTEST_TYPE.PRIVATE) {\n          dispatch('getContestAccess')\n        }\n      }, err => {\n        reject(err)\n      })\n    })\n  },\n  getContestProblems ({commit, rootState}) {\n    return new Promise((resolve, reject) => {\n      api.getContestProblemList(rootState.route.params.contestID).then(res => {\n        res.data.data.sort((a, b) => {\n          if (a._id === b._id) {\n            return 0\n          } else if (a._id > b._id) {\n            return 1\n          }\n          return -1\n        })\n        commit(types.CHANGE_CONTEST_PROBLEMS, {contestProblems: res.data.data})\n        resolve(res)\n      }, () => {\n        commit(types.CHANGE_CONTEST_PROBLEMS, {contestProblems: []})\n      })\n    })\n  },\n  getContestAccess ({commit, rootState}) {\n    return new Promise((resolve, reject) => {\n      api.getContestAccess(rootState.route.params.contestID).then(res => {\n        commit(types.CONTEST_ACCESS, {access: res.data.data.access})\n        resolve(res)\n      }).catch()\n    })\n  }\n}\n\nexport default {\n  state,\n  mutations,\n  getters,\n  actions\n}\n"
  },
  {
    "path": "src/store/modules/user.js",
    "content": "import types from '../types'\nimport api from '@oj/api'\nimport storage from '@/utils/storage'\nimport i18n from '@/i18n'\nimport { STORAGE_KEY, USER_TYPE, PROBLEM_PERMISSION } from '@/utils/constants'\n\nconst state = {\n  profile: {}\n}\n\nconst getters = {\n  user: state => state.profile.user || {},\n  profile: state => state.profile,\n  isAuthenticated: (state, getters) => {\n    return !!getters.user.id\n  },\n  isAdminRole: (state, getters) => {\n    return getters.user.admin_type === USER_TYPE.ADMIN ||\n      getters.user.admin_type === USER_TYPE.SUPER_ADMIN\n  },\n  isSuperAdmin: (state, getters) => {\n    return getters.user.admin_type === USER_TYPE.SUPER_ADMIN\n  },\n  hasProblemPermission: (state, getters) => {\n    return getters.user.problem_permission !== PROBLEM_PERMISSION.NONE\n  }\n}\n\nconst mutations = {\n  [types.CHANGE_PROFILE] (state, {profile}) {\n    state.profile = profile\n    if (profile.language) {\n      i18n.locale = profile.language\n    }\n    storage.set(STORAGE_KEY.AUTHED, !!profile.user)\n  }\n}\n\nconst actions = {\n  getProfile ({commit}) {\n    api.getUserInfo().then(res => {\n      commit(types.CHANGE_PROFILE, {\n        profile: res.data.data || {}\n      })\n    })\n  },\n  clearProfile ({commit}) {\n    commit(types.CHANGE_PROFILE, {\n      profile: {}\n    })\n    storage.clear()\n  }\n}\n\nexport default {\n  state,\n  getters,\n  actions,\n  mutations\n}\n"
  },
  {
    "path": "src/store/types.js",
    "content": "function keyMirror (obj) {\n  if (obj instanceof Object) {\n    var _obj = Object.assign({}, obj)\n    var _keyArray = Object.keys(obj)\n    _keyArray.forEach(key => {\n      _obj[key] = key\n    })\n    return _obj\n  }\n}\n\nexport default keyMirror({\n  'CHANGE_PROFILE': null,\n  'CHANGE_MODAL_STATUS': null,\n  'UPDATE_WEBSITE_CONF': null,\n\n  'NOW': null,\n  'NOW_ADD_1S': null,\n  'CHANGE_CONTEST': null,\n  'CHANGE_CONTEST_PROBLEMS': null,\n  'CHANGE_CONTEST_ITEM_VISIBLE': null,\n  'CHANGE_RANK_FORCE_UPDATE': null,\n  'CHANGE_CONTEST_RANK_LIMIT': null,\n  'CONTEST_ACCESS': null,\n  'CLEAR_CONTEST': null\n})\n"
  },
  {
    "path": "src/styles/common.less",
    "content": "html {\n  background-color: #eee;\n}\n\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: \"Helvetica Neue\", Helvetica, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", \"微软雅黑\", Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  background-color: #eee;\n  min-width: 900px;\n}\n\n.flex-container {\n  display: flex;\n  width: 100%;\n  max-width: 100%;\n  justify-content: space-around;\n  align-items: flex-start;\n  flex-flow: row nowrap;\n}\n\n.section-title {\n  font-size: 21px;\n  font-weight: 500;\n  padding-top: 10px;\n  padding-bottom: 20px;\n  line-height: 30px;\n}\n\n.separator {\n  display: block;\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 50%;\n  border: 1px dashed #eee;\n}\n\n.oj-captcha {\n  display: flex;\n  flex-wrap: nowrap;\n  justify-content: space-between;\n  width: 100%;\n  height: 36px;\n  .oj-captcha-code {\n    flex: auto;\n  }\n  .oj-captcha-img {\n    margin-left: 10px;\n    padding: 3px;\n    flex: initial;\n  }\n}\n\n.oj-relative {\n  position: relative;\n}\n\na.emphasis{\n  color: #495060;\n  &:hover {\n    color: #2d8cf0;\n  }\n}\n\n// for mathjax\n.MathJax {\n  outline: 0;\n}\n\n.MathJax_Display {\n  overflow-x: auto;\n  overflow-y: hidden;\n}\n\n.markdown-body {\n  @import './markdown.less';\n}\n\n@keyframes fadeInUp {\n  from {\n    opacity: 0;\n    transform: translate(0, 50px);\n  }\n\n  to {\n    opacity: 1;\n    transform: none;\n  }\n}\n\n@keyframes fadeIn {\n  from {\n    opacity: 0;\n  }\n\n  to {\n    opacity: 1;\n  }\n}\n\n\n"
  },
  {
    "path": "src/styles/index.less",
    "content": "@import './common.less';\n@import './iview-custom.less';\n"
  },
  {
    "path": "src/styles/iview-custom.less",
    "content": "table {\n  width: 100% !important;\n}\n.auto-resize {\n  table {\n    table-layout: auto !important;\n  }\n}\n.ivu-table-wrapper {\n  border: none;\n}\n\n.ivu-table td {\n  border-bottom-color: #dddddd;\n}\n\n.ivu-card-head {\n  border-bottom-width: 0;\n}\n\n.ivu-table td {\n  border-bottom-color: #dddddd;\n}\n\n.ivu-table .first-ac {\n  background-color: #33CC99;\n  color: #3c763d;\n}\n\n.ivu-table .ac {\n  background-color: #dff0d8;;\n  color: #3c763d;\n}\n\n.ivu-table .wa {\n  color: #a94442;\n  background-color: #f2dede;\n}\n\n.ivu-modal-footer {\n  border-top-width: 0;\n  padding: 0 18px 20px 18px;\n}\n.ivu-modal-body {\n  padding: 18px;\n}\n"
  },
  {
    "path": "src/styles/markdown.less",
    "content": "h1, h2, h3, h4 {\n  color: #111111;\n  font-weight: 400;\n}\n\nh1, h2, h3, h4, h5, p {\n  margin-bottom: 15px;\n  padding: 0;\n}\n\nh1 {\n  font-size: 28px;\n}\n\nh2 {\n  font-size: 24px;\n}\n\nh3 {\n  font-size: 20px;\n}\n\nh4 {\n  font-size: 18px;\n}\n\nh5 {\n  font-size: 14px;\n}\n\na {\n  color: #0099ff;\n  margin: 0;\n  padding: 0;\n  vertical-align: baseline;\n}\n\nul, ol {\n  padding: 0;\n  margin: 10px 20px;\n}\n\nul {\n  list-style-type: disc;\n}\n\nol {\n  list-style-type: decimal;\n}\n\nli {\n  line-height: 24px;\n}\n\nli ul, li ul {\n  margin-left: 24px;\n}\n\np, ul, ol {\n  font-size: 16px;\n  line-height: 24px;\n}\n\npre {\n  padding: 5px 10px;\n  white-space: pre-wrap;\n  margin-top: 15px;\n  margin-bottom: 15px;\n  background: #f8f8f9;\n  border: 1px dashed #e9eaec;\n}\n\ncode {\n  font-size: 90%;\n  padding: 2px 5px;\n  margin: 0;\n  background-color: rgba(27, 31, 35, 0.05);\n  border-radius: 3px;\n  line-height: 1.5;\n}\n\npre > code {\n  padding: 0;\n  margin: 0;\n  font-size: 100%;\n  word-break: normal;\n  white-space: pre;\n  background: transparent;\n  border: 0;\n}\n\naside {\n  display: block;\n  float: right;\n  width: 390px;\n}\n\nblockquote {\n  border-left: 3px solid #bbbec4;\n  padding-left: 10px;\n  margin-top: 10px;\n  margin-bottom: 10px;\n  color: #7b7b7b;\n}\n\nhr {\n  width: 540px;\n  text-align: left;\n  margin: 0 auto 0 0;\n  color: #999;\n}\n\ntable {\n  border-collapse: collapse;\n  margin: 1em 1em;\n  border: 1px solid #ccc;\n  thead {\n    background-color: #eee;\n    td {\n      color: #666;\n    }\n  }\n  td {\n    padding: 0.5em 1em;\n    border: 1px solid #ccc;\n  }\n}\n"
  },
  {
    "path": "src/utils/constants.js",
    "content": "export const JUDGE_STATUS = {\n  '-2': {\n    name: 'Compile Error',\n    short: 'CE',\n    color: 'yellow',\n    type: 'warning'\n  },\n  '-1': {\n    name: 'Wrong Answer',\n    short: 'WA',\n    color: 'red',\n    type: 'error'\n  },\n  '0': {\n    name: 'Accepted',\n    short: 'AC',\n    color: 'green',\n    type: 'success'\n  },\n  '1': {\n    name: 'Time Limit Exceeded',\n    short: 'TLE',\n    color: 'red',\n    type: 'error'\n  },\n  '2': {\n    name: 'Time Limit Exceeded',\n    short: 'TLE',\n    color: 'red',\n    type: 'error'\n  },\n  '3': {\n    name: 'Memory Limit Exceeded',\n    short: 'MLE',\n    color: 'red',\n    type: 'error'\n  },\n  '4': {\n    name: 'Runtime Error',\n    short: 'RE',\n    color: 'red',\n    type: 'error'\n  },\n  '5': {\n    name: 'System Error',\n    short: 'SE',\n    color: 'red',\n    type: 'error'\n  },\n  '6': {\n    name: 'Pending',\n    color: 'yellow',\n    type: 'warning'\n  },\n  '7': {\n    name: 'Judging',\n    color: 'blue',\n    type: 'info'\n  },\n  '8': {\n    name: 'Partial Accepted',\n    short: 'PAC',\n    color: 'blue',\n    type: 'info'\n  },\n  '9': {\n    name: 'Submitting',\n    color: 'yellow',\n    type: 'warning'\n  }\n}\n\nexport const CONTEST_STATUS = {\n  'NOT_START': '1',\n  'UNDERWAY': '0',\n  'ENDED': '-1'\n}\n\nexport const CONTEST_STATUS_REVERSE = {\n  '1': {\n    name: 'Not Started',\n    color: 'yellow'\n  },\n  '0': {\n    name: 'Underway',\n    color: 'green'\n  },\n  '-1': {\n    name: 'Ended',\n    color: 'red'\n  }\n}\n\nexport const RULE_TYPE = {\n  ACM: 'ACM',\n  OI: 'OI'\n}\n\nexport const CONTEST_TYPE = {\n  PUBLIC: 'Public',\n  PRIVATE: 'Password Protected'\n}\n\nexport const USER_TYPE = {\n  REGULAR_USER: 'Regular User',\n  ADMIN: 'Admin',\n  SUPER_ADMIN: 'Super Admin'\n}\n\nexport const PROBLEM_PERMISSION = {\n  NONE: 'None',\n  OWN: 'Own',\n  ALL: 'All'\n}\n\nexport const STORAGE_KEY = {\n  AUTHED: 'authed',\n  PROBLEM_CODE: 'problemCode',\n  languages: 'languages'\n}\n\nexport function buildProblemCodeKey (problemID, contestID = null) {\n  if (contestID) {\n    return `${STORAGE_KEY.PROBLEM_CODE}_${contestID}_${problemID}`\n  }\n  return `${STORAGE_KEY.PROBLEM_CODE}_NaN_${problemID}`\n}\n\nexport const GOOGLE_ANALYTICS_ID = 'UA-111499601-1'\n"
  },
  {
    "path": "src/utils/filters.js",
    "content": "import moment from 'moment'\nimport utils from './utils'\nimport time from './time'\n\n// 友好显示时间\nfunction fromNow (time) {\n  return moment(time * 3).fromNow()\n}\n\nexport default {\n  submissionMemory: utils.submissionMemoryFormat,\n  submissionTime: utils.submissionTimeFormat,\n  localtime: time.utcToLocal,\n  fromNow: fromNow\n}\n"
  },
  {
    "path": "src/utils/sentry.js",
    "content": "import Vue from 'vue'\nimport Raven from 'raven-js'\nimport RavenVue from 'raven-js/plugins/vue'\n\nconst options = {\n  release: process.env.VERSION,\n  ignoreUrls: [\n    // Chrome extensions\n    /extensions\\//i,\n    /^chrome:\\/\\//i,\n    // Firefox extensions\n    /^resource:\\/\\//i,\n    // Ignore Google flakiness\n    /\\/(gtm|ga|analytics)\\.js/i\n  ]\n}\n\nRaven\n  .config('https://6234a51e61a743b089ed64c51d2f6ea9@sentry.io/258234', options)\n  .addPlugin(RavenVue, Vue)\n  .install()\n\nRaven.setUserContext({\n  version: process.env.VERSION,\n  location: window.location\n})\n"
  },
  {
    "path": "src/utils/storage.js",
    "content": "const localStorage = window.localStorage\n\nexport default {\n  name: 'storage',\n\n  /**\n   * save value(Object) to key\n   * @param {string} key 键\n   * @param {Object} value 值\n   */\n  set (key, value) {\n    localStorage.setItem(key, JSON.stringify(value))\n  },\n\n  /**\n   * get value(Object) by key\n   * @param {string} key 键\n   * @return {Object}\n   */\n  get (key) {\n    return JSON.parse(localStorage.getItem(key)) || null\n  },\n\n  /**\n   * remove key from localStorage\n   * @param {string} key 键\n   */\n  remove (key) {\n    localStorage.removeItem(key)\n  },\n  /**\n   * clear all\n   */\n  clear () {\n    localStorage.clear()\n  }\n}\n"
  },
  {
    "path": "src/utils/time.js",
    "content": "import moment from 'moment'\n\n// convert utc time to localtime\nfunction utcToLocal (utcDt, format = 'YYYY-M-D  HH:mm:ss') {\n  return moment.utc(utcDt).local().format(format)\n}\n\n// get duration from startTime to endTime, return like 3 days, 2 hours, one year ..\nfunction duration (startTime, endTime) {\n  let start = moment(startTime)\n  let end = moment(endTime)\n  let duration = moment.duration(start.diff(end, 'seconds'), 'seconds')\n  if (duration.days() !== 0) {\n    return duration.humanize()\n  }\n  return Math.abs(duration.asHours().toFixed(1)) + ' hours'\n}\n\nfunction secondFormat (seconds) {\n  let m = moment.duration(seconds, 'seconds')\n  return Math.floor(m.asHours()) + ':' + m.minutes() + ':' + m.seconds()\n}\n\nexport default {\n  utcToLocal: utcToLocal,\n  duration: duration,\n  secondFormat: secondFormat\n}\n"
  },
  {
    "path": "src/utils/utils.js",
    "content": "import Vue from 'vue'\nimport storage from '@/utils/storage'\nimport { STORAGE_KEY } from '@/utils/constants'\nimport ojAPI from '@oj/api'\n\nfunction submissionMemoryFormat (memory) {\n  if (memory === undefined) return '--'\n  // 1048576 = 1024 * 1024\n  let t = parseInt(memory) / 1048576\n  return String(t.toFixed(0)) + 'MB'\n}\n\nfunction submissionTimeFormat (time) {\n  if (time === undefined) return '--'\n  return time + 'ms'\n}\n\nfunction getACRate (acCount, totalCount) {\n  let rate = totalCount === 0 ? 0.00 : (acCount / totalCount * 100).toFixed(2)\n  return String(rate) + '%'\n}\n\n// 去掉值为空的项，返回object\nfunction filterEmptyValue (object) {\n  let query = {}\n  Object.keys(object).forEach(key => {\n    if (object[key] || object[key] === 0 || object[key] === false) {\n      query[key] = object[key]\n    }\n  })\n  return query\n}\n\n// 按指定字符数截断添加换行，非英文字符按指定字符的半数截断\nfunction breakLongWords (value, length = 16) {\n  let re\n  if (escape(value).indexOf('%u') === -1) {\n    // 没有中文\n    re = new RegExp('(.{' + length + '})', 'g')\n  } else {\n    // 中文字符\n    re = new RegExp('(.{' + (length / 2 + 1) + '})', 'g')\n  }\n  return value.replace(re, '$1\\n')\n}\n\nfunction downloadFile (url) {\n  return new Promise((resolve, reject) => {\n    Vue.prototype.$http.get(url, {responseType: 'blob'}).then(resp => {\n      let headers = resp.headers\n      if (headers['content-type'].indexOf('json') !== -1) {\n        let fr = new window.FileReader()\n        if (resp.data.error) {\n          Vue.prototype.$error(resp.data.error)\n        } else {\n          Vue.prototype.$error('Invalid file format')\n        }\n        fr.onload = (event) => {\n          let data = JSON.parse(event.target.result)\n          if (data.error) {\n            Vue.prototype.$error(data.data)\n          } else {\n            Vue.prototype.$error('Invalid file format')\n          }\n        }\n        let b = new window.Blob([resp.data], {type: 'application/json'})\n        fr.readAsText(b)\n        return\n      }\n      let link = document.createElement('a')\n      link.href = window.URL.createObjectURL(new window.Blob([resp.data], {type: headers['content-type']}))\n      link.download = (headers['content-disposition'] || '').split('filename=')[1]\n      document.body.appendChild(link)\n      link.click()\n      link.remove()\n      resolve()\n    }).catch(() => {})\n  })\n}\n\nfunction getLanguages () {\n  return new Promise((resolve, reject) => {\n    let languages = storage.get(STORAGE_KEY.languages)\n    if (languages) {\n      resolve(languages)\n    }\n    ojAPI.getLanguages().then(res => {\n      let languages = res.data.data.languages\n      storage.set(STORAGE_KEY.languages, languages)\n      resolve(languages)\n    }, err => {\n      reject(err)\n    })\n  })\n}\n\nexport default {\n  submissionMemoryFormat: submissionMemoryFormat,\n  submissionTimeFormat: submissionTimeFormat,\n  getACRate: getACRate,\n  filterEmptyValue: filterEmptyValue,\n  breakLongWords: breakLongWords,\n  downloadFile: downloadFile,\n  getLanguages: getLanguages\n}\n"
  },
  {
    "path": "static/css/loader.css",
    "content": "@-webkit-keyframes enter {\n  0% {\n    opacity: 0;\n    top: -10px;\n  }\n  5% {\n    opacity: 1;\n    top: 0px;\n  }\n  50.9% {\n    opacity: 1;\n    top: 0px;\n  }\n  55.9% {\n    opacity: 0;\n    top: 10px;\n  }\n}\n@keyframes enter {\n  0% {\n    opacity: 0;\n    top: -10px;\n  }\n  5% {\n    opacity: 1;\n    top: 0px;\n  }\n  50.9% {\n    opacity: 1;\n    top: 0px;\n  }\n  55.9% {\n    opacity: 0;\n    top: 10px;\n  }\n}\n@-moz-keyframes enter {\n  0% {\n    opacity: 0;\n    top: -10px;\n  }\n  5% {\n    opacity: 1;\n    top: 0px;\n  }\n  50.9% {\n    opacity: 1;\n    top: 0px;\n  }\n  55.9% {\n    opacity: 0;\n    top: 10px;\n  }\n}\nbody {\n  background: #f8f8f9;\n}\n\n#app-loader {\n  position: absolute;\n  left: 50%;\n  top: 50%;\n  margin-left: -27.5px;\n  margin-top: -27.5px;\n}\n#app-loader .square {\n  background: #2d8cf0;\n  width: 15px;\n  height: 15px;\n  float: left;\n  top: -10px;\n  margin-right: 5px;\n  margin-top: 5px;\n  position: relative;\n  opacity: 0;\n  -webkit-animation: enter 6s infinite;\n  animation: enter 6s infinite;\n}\n#app-loader .enter {\n  top: 0px;\n  opacity: 1;\n}\n#app-loader .square:nth-child(1) {\n  -webkit-animation-delay: 1.8s;\n  -moz-animation-delay: 1.8s;\n  animation-delay: 1.8s;\n}\n#app-loader .square:nth-child(2) {\n  -webkit-animation-delay: 2.1s;\n  -moz-animation-delay: 2.1s;\n  animation-delay: 2.1s;\n}\n#app-loader .square:nth-child(3) {\n  -webkit-animation-delay: 2.4s;\n  -moz-animation-delay: 2.4s;\n  animation-delay: 2.4s;\n  background: #ff9900;\n}\n#app-loader .square:nth-child(4) {\n  -webkit-animation-delay: 0.9s;\n  -moz-animation-delay: 0.9s;\n  animation-delay: 0.9s;\n}\n#app-loader .square:nth-child(5) {\n  -webkit-animation-delay: 1.2s;\n  -moz-animation-delay: 1.2s;\n  animation-delay: 1.2s;\n}\n#app-loader .square:nth-child(6) {\n  -webkit-animation-delay: 1.5s;\n  -moz-animation-delay: 1.5s;\n  animation-delay: 1.5s;\n}\n#app-loader .square:nth-child(8) {\n  -webkit-animation-delay: 0.3s;\n  -moz-animation-delay: 0.3s;\n  animation-delay: 0.3s;\n}\n#app-loader .square:nth-child(9) {\n  -webkit-animation-delay: 0.6s;\n  -moz-animation-delay: 0.6s;\n  animation-delay: 0.6s;\n}\n#app-loader .clear {\n  clear: both;\n}\n#app-loader .last {\n  margin-right: 0;\n}\n"
  }
]