Full Code of QingdaoU/OnlineJudgeFE for AI

master 44fa4744c21a cached
135 files
416.1 KB
113.8k tokens
178 symbols
1 requests
Download .txt
Showing preview only (460K chars total). Download the full file or copy to clipboard to get everything.
Repository: QingdaoU/OnlineJudgeFE
Branch: master
Commit: 44fa4744c21a
Files: 135
Total size: 416.1 KB

Directory structure:
gitextract_o9t9gx4q/

├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── .travis.yml
├── LICENSE
├── README.md
├── build/
│   ├── build.js
│   ├── check-versions.js
│   ├── dev-client.js
│   ├── dev-server.js
│   ├── utils.js
│   ├── vue-loader.conf.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   ├── webpack.dll.conf.js
│   └── webpack.prod.conf.js
├── config/
│   ├── dev.env.js
│   ├── index.js
│   └── prod.env.js
├── deploy/
│   ├── Dockerfile
│   ├── nginx.conf
│   ├── run.sh
│   └── sentry_release.sh
├── package.json
├── src/
│   ├── i18n/
│   │   ├── admin/
│   │   │   ├── en-US.js
│   │   │   ├── zh-CN.js
│   │   │   └── zh-TW.js
│   │   ├── index.js
│   │   └── oj/
│   │       ├── en-US.js
│   │       ├── zh-CN.js
│   │       └── zh-TW.js
│   ├── pages/
│   │   ├── admin/
│   │   │   ├── App.vue
│   │   │   ├── api.js
│   │   │   ├── components/
│   │   │   │   ├── Accordion.vue
│   │   │   │   ├── CodeMirror.vue
│   │   │   │   ├── KatexEditor.vue
│   │   │   │   ├── Panel.vue
│   │   │   │   ├── ScreenFull.vue
│   │   │   │   ├── SideMenu.vue
│   │   │   │   ├── Simditor.vue
│   │   │   │   ├── TopNav.vue
│   │   │   │   ├── btn/
│   │   │   │   │   ├── Cancel.vue
│   │   │   │   │   ├── IconBtn.vue
│   │   │   │   │   └── Save.vue
│   │   │   │   ├── infoCard.vue
│   │   │   │   └── simditor-file-upload.js
│   │   │   ├── index.html
│   │   │   ├── index.js
│   │   │   ├── router.js
│   │   │   ├── style.less
│   │   │   └── views/
│   │   │       ├── Home.vue
│   │   │       ├── contest/
│   │   │       │   ├── Contest.vue
│   │   │       │   └── ContestList.vue
│   │   │       ├── general/
│   │   │       │   ├── Announcement.vue
│   │   │       │   ├── Conf.vue
│   │   │       │   ├── Dashboard.vue
│   │   │       │   ├── JudgeServer.vue
│   │   │       │   ├── Login.vue
│   │   │       │   ├── PruneTestCase.vue
│   │   │       │   └── User.vue
│   │   │       ├── index.js
│   │   │       └── problem/
│   │   │           ├── AddPublicProblem.vue
│   │   │           ├── ImportAndExport.vue
│   │   │           ├── Problem.vue
│   │   │           └── ProblemList.vue
│   │   └── oj/
│   │       ├── App.vue
│   │       ├── api.js
│   │       ├── components/
│   │       │   ├── CodeMirror.vue
│   │       │   ├── Highlight.vue
│   │       │   ├── NavBar.vue
│   │       │   ├── Pagination.vue
│   │       │   ├── Panel.vue
│   │       │   ├── mixins/
│   │       │   │   ├── emitter.js
│   │       │   │   ├── form.js
│   │       │   │   ├── index.js
│   │       │   │   └── problem.js
│   │       │   └── verticalMenu/
│   │       │       ├── verticalMenu-item.vue
│   │       │       └── verticalMenu.vue
│   │       ├── index.html
│   │       ├── index.js
│   │       ├── router/
│   │       │   ├── index.js
│   │       │   └── routes.js
│   │       └── views/
│   │           ├── contest/
│   │           │   ├── ContestDetail.vue
│   │           │   ├── ContestList.vue
│   │           │   ├── children/
│   │           │   │   ├── ACMContestRank.vue
│   │           │   │   ├── ACMHelper.vue
│   │           │   │   ├── ContestProblemList.vue
│   │           │   │   ├── ContestRank.vue
│   │           │   │   ├── OIContestRank.vue
│   │           │   │   └── contestRankMixin.js
│   │           │   └── index.js
│   │           ├── general/
│   │           │   ├── 404.vue
│   │           │   ├── Announcements.vue
│   │           │   └── Home.vue
│   │           ├── help/
│   │           │   ├── About.vue
│   │           │   └── FAQ.vue
│   │           ├── index.js
│   │           ├── problem/
│   │           │   ├── Problem.vue
│   │           │   ├── ProblemList.vue
│   │           │   └── chartData.js
│   │           ├── rank/
│   │           │   ├── ACMRank.vue
│   │           │   └── OIRank.vue
│   │           ├── setting/
│   │           │   ├── Settings.vue
│   │           │   ├── children/
│   │           │   │   ├── AccountSetting.vue
│   │           │   │   ├── ProfileSetting.vue
│   │           │   │   └── SecuritySetting.vue
│   │           │   └── index.js
│   │           ├── submission/
│   │           │   ├── SubmissionDetails.vue
│   │           │   └── SubmissionList.vue
│   │           └── user/
│   │               ├── ApplyResetPassword.vue
│   │               ├── Login.vue
│   │               ├── Logout.vue
│   │               ├── Register.vue
│   │               ├── ResetPassword.vue
│   │               └── UserHome.vue
│   ├── plugins/
│   │   ├── highlight.js
│   │   └── katex.js
│   ├── store/
│   │   ├── index.js
│   │   ├── modules/
│   │   │   ├── contest.js
│   │   │   └── user.js
│   │   └── types.js
│   ├── styles/
│   │   ├── common.less
│   │   ├── index.less
│   │   ├── iview-custom.less
│   │   └── markdown.less
│   └── utils/
│       ├── constants.js
│       ├── filters.js
│       ├── sentry.js
│       ├── storage.js
│       ├── time.js
│       └── utils.js
└── static/
    └── css/
        └── loader.css

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 9"]
      },
      "useBuiltIns": true
    }],
    "stage-2"
  ],
  "plugins": [
    "transform-runtime",
    "syntax-dynamic-import"
  ],
  "env": {
    "test": {
      "presets": [
        "env",
        "stage-2"
      ],
      "plugins": [
        "istanbul"
      ]
    }
  }
}


================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: .eslintignore
================================================
build/*.js
config/*.js


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  parser: 'babel-eslint',
  parserOptions: {
    sourceType: 'module'
  },
  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
  extends: 'standard',
  // required to lint *.vue files
  plugins: [
    'html'
  ],
  // add your custom rules here
  'rules': {
    // allow paren-less arrow functions
    'arrow-parens': 0,
    // allow async-await
    'generator-star-spacing': 0,
    // allow debugger during development
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    "no-irregular-whitespace": ["error", {
      "skipComments": true,
      "skipTemplates": true
    }],
    "no-unused-vars": ["warn"]
  }
}


================================================
FILE: .gitignore
================================================
package-lock.json

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
dist/

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# editor
.vscode
.idea

# test_code
test.vue

# build
vendor-manifest.json
vendor.dll*.js



================================================
FILE: .postcssrc.js
================================================
// https://github.com/michael-ciniawsky/postcss-load-config

module.exports = {
  "plugins": {
    // to edit target browsers: use "browserlist" field in package.json
    "autoprefixer": {}
  }
}


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - 8.12.0
sudo: required
services:
  - docker
env:
  global:
    - CXX=g++-4.8
  matrix:
    - USE_SENTRY=1
#    - USE_SENTRY=0
addons:
  apt:
    sources:
      - ubuntu-toolchain-r-test
    packages:
      - g++-4.8
before_install:
  - docker pull getsentry/sentry-cli

script:
  - npm install
  - npm run build:dll
  - npm run build

before_deploy:
  - bash deploy/sentry_release.sh
  - find dist/ -type f -name "*.map" -delete
  - zip -r dist.zip dist

deploy:
  provider: releases
  skip_cleanup: true
  api_key:
    secure: I+BjkiPd+7XnMo1qWFhyz2xqElBLlGuCx1ZamvEa61zk4HPAnfWDPvpxXWihLdE4WDIkrOTRQ6Nh6GlVz+2uFugtdAlrxEWjU3bhadf5hAPNL2faNd52CN2qRNOuaWZmIkY4KUDmKoxvcFVQCKqMCxpghlNT7IJLwaC5wjogMeQKERXSa4rXl/ZGHdZzpkPo8SIEIy7hwQcHDl0ckm3t8wlXe6/xCRR2YoI3enZE15oJ15PChEcYbI6kUHNg3iIAddAAykqU0PnBtGSaPkl2JU90f4Rs62wW626wXyBs39ZU2rSbooyKF7OgFtS7KeTbYxyINBta8JH45plE/HVB3U3wy/8dBneZYr6ySGtSZSV10ortW59Al6Pifyo1na6tgkXSrckUOh1HFSiOsN6k46RXo6T1L1w1P8cUCJ2WYJksHJqBXnQmKbol9x3Gz6fQHR6yA5ToczKmg2ow549Q3g3oRb6RjRzXNkYu9sAZ2mhQ1fIjm7GkYPQvHzze+qwBxeX/ysFwIUNjUnK6u6EZunShz6fF9NEsDxzfUXPDOfB28MmxGkIi6TuT21F8tKfCG0C0CqLWBBve2agU9gutvIA1aY9i5K0YnlXPah1NYYzDPlk1RepWzJrq9VzGz3HxwY++cm6vR/7o9V+3WVpMIWHjnYwDTpK7pfm3VYbiLlg=
  file: dist.zip
  on:
    repo: QingdaoU/OnlineJudgeFE
    all_branches: true
    tags: true


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2017-present OnineJudge

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

The MIT License (MIT)

Copyright (c) 2013-present, Yuxi (Evan) You

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

The MIT License (MIT)


Copyright (c) 2016-present iView

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

The MIT License (MIT)

Copyright (c) 2016 ElemeFE

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# OnlineJudge Front End
[![vue](https://img.shields.io/badge/vue-2.5.13-blue.svg?style=flat-square)](https://github.com/vuejs/vue)
[![vuex](https://img.shields.io/badge/vuex-3.0.1-blue.svg?style=flat-square)](https://vuex.vuejs.org/)
[![echarts](https://img.shields.io/badge/echarts-3.8.3-blue.svg?style=flat-square)](https://github.com/ecomfe/echarts)
[![iview](https://img.shields.io/badge/iview-2.8.0-blue.svg?style=flat-square)](https://github.com/iview/iview)
[![element-ui](https://img.shields.io/badge/element-2.0.9-blue.svg?style=flat-square)](https://github.com/ElemeFE/element)
[![Build Status](https://travis-ci.org/QingdaoU/OnlineJudgeFE.svg?branch=master)](https://travis-ci.org/QingdaoU/OnlineJudgeFE)

>### A multiple pages app built for OnlineJudge. [Demo](https://qduoj.com)

## Features

+ Webpack3 multiple pages with bundle size optimization
+ Easy use simditor & Nice codemirror editor
+ Amazing charting and visualization(echarts)
+ User-friendly operation
+ Quite beautiful:)

## Get Started

Install nodejs **v8.12.0** first.

### Linux

```bash
npm install
# we use webpack DllReference to decrease the build time,
# this command only needs execute once unless you upgrade the package in build/webpack.dll.conf.js
export NODE_ENV=development 
npm run build:dll

# the dev-server will set proxy table to your backend
export TARGET=http://Your-backend

# serve with hot reload at localhost:8080
npm run dev
```
### Windows

```bash
npm install
# we use webpack DllReference to decrease the build time,
# this command only needs execute once unless you upgrade the package in build/webpack.dll.conf.js
set NODE_ENV=development 
npm run build:dll

# the dev-server will set proxy table to your backend
set TARGET=http://Your-backend

# serve with hot reload at localhost:8080
npm run dev
```

## Screenshots

[Check here.](https://github.com/QingdaoU/OnlineJudge)

## Browser Support

Modern browsers and Internet Explorer 10+.

## LICENSE

[MIT](http://opensource.org/licenses/MIT)


================================================
FILE: build/build.js
================================================
'use strict'
require('./check-versions')()

process.env.NODE_ENV = 'production'

const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('../config')
const webpackConfig = require('./webpack.prod.conf')

const spinner = ora('building for production...')
spinner.start()

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, function (err, stats) {
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false
    }) + '\n\n')

    if (stats.hasErrors()) {
      console.log(chalk.red('  Build failed with errors.\n'))
      process.exit(1)
    }

    console.log(chalk.cyan('  Congratulations, the project built complete without error\n'))
    console.log(chalk.yellow(
      ' You can now check the onlinejudge in http://YouIP/'
    ))
  })
})


================================================
FILE: build/check-versions.js
================================================
'use strict'
const chalk = require('chalk')
const semver = require('semver')
const packageConfig = require('../package.json')
const shell = require('shelljs')
function exec (cmd) {
  return require('child_process').execSync(cmd).toString().trim()
}

const versionRequirements = [
  {
    name: 'node',
    currentVersion: semver.clean(process.version),
    versionRequirement: packageConfig.engines.node
  }
]

if (shell.which('npm')) {
  versionRequirements.push({
    name: 'npm',
    currentVersion: exec('npm --version'),
    versionRequirement: packageConfig.engines.npm
  })
}

module.exports = function () {
  const warnings = []
  for (let i = 0; i < versionRequirements.length; i++) {
    const mod = versionRequirements[i]
    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
      warnings.push(mod.name + ': ' +
        chalk.red(mod.currentVersion) + ' should be ' +
        chalk.green(mod.versionRequirement)
      )
    }
  }

  if (warnings.length) {
    console.log('')
    console.log(chalk.yellow('To use this template, you must update following to modules:'))
    console.log()
    for (let i = 0; i < warnings.length; i++) {
      const warning = warnings[i]
      console.log('  ' + warning)
    }
    console.log()
    process.exit(1)
  }
}


================================================
FILE: build/dev-client.js
================================================
/* eslint-disable */
'use strict'
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')

hotClient.subscribe(function (event) {
  if (event.action === 'reload') {
    window.location.reload()
  }
})


================================================
FILE: build/dev-server.js
================================================
'use strict'
require('./check-versions')()

const config = require('../config')
if (!process.env.NODE_ENV) {
  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}

const opn = require('opn')
const path = require('path')
const express = require('express')
const webpack = require('webpack')
const proxyMiddleware = require('http-proxy-middleware')
const webpackConfig = require('./webpack.dev.conf')

// default port where dev server listens for incoming traffic
const port = process.env.PORT || config.dev.port
// automatically open browser, if not set will be false
const autoOpenBrowser = !!config.dev.autoOpenBrowser
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
const proxyTable = config.dev.proxyTable

const app = express()
const compiler = webpack(webpackConfig)

const devMiddleware = require('webpack-dev-middleware')(compiler, {
  publicPath: webpackConfig.output.publicPath,
  quiet: true
})

const hotMiddleware = require('webpack-hot-middleware')(compiler, {
  log: false,
  heartbeat: 2000
})
// force page reload when html-webpack-plugin template changes
// currently disabled until this is resolved:
// https://github.com/jantimon/html-webpack-plugin/issues/680
// compiler.plugin('compilation', function (compilation) {
//   compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
//     hotMiddleware.publish({ action: 'reload' })
//     cb()
//   })
// })

// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)

// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
  let options = proxyTable[context]
  if (typeof options === 'string') {
    options = { target: options }
  }
  app.use(proxyMiddleware(options.filter || context, options))
})

// handle fallback for HTML5 history API
const rewrites = {
  rewrites: [{
    from: '/admin/', // 正则或者字符串
    to: '/admin/index.html', // 字符串或者函数
  }]
}
const historyMiddleware = require('connect-history-api-fallback')(rewrites);
app.use(historyMiddleware)

// serve webpack bundle output
app.use(devMiddleware)

// serve pure static assets
const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))

const uri = 'http://localhost:' + port

var _resolve
var _reject
var readyPromise = new Promise((resolve, reject) => {
  _resolve = resolve
  _reject = reject
})

var server
var portfinder = require('portfinder')
portfinder.basePort = port

console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
  portfinder.getPort((err, port) => {
    if (err) {
      _reject(err)
    }
    process.env.PORT = port
    var uri = 'http://localhost:' + port
    console.log('> Listening at ' + uri + '\n')
    // when env is testing, don't need open it
    if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
      opn(uri)
    }
    server = app.listen(port)
    _resolve()
  })
})

module.exports = {
  ready: readyPromise,
  close: () => {
    server.close()
  }
}


================================================
FILE: build/utils.js
================================================
'use strict'
const path = require('path')
const config = require('../config')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

exports.assetsPath = function (_path) {
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
    ? config.build.assetsSubDirectory
    : config.dev.assetsSubDirectory
  return path.posix.join(assetsSubDirectory, _path)
}

exports.cssLoaders = function (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      minimize: process.env.NODE_ENV === 'production',
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders (loader, loaderOptions) {
    const loaders = [cssLoader]
    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', {indentedSyntax: true}),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
  const output = []
  const loaders = exports.cssLoaders(options)
  for (const extension in loaders) {
    const loader = loaders[extension]
    output.push({
      test: new RegExp('\\.' + extension + '$'),
      use: loader
    })
  }
  return output
}

exports.getNodeEnv = function () {
  const NODE_ENV = process.env.NODE_ENV
  return NODE_ENV ? NODE_ENV: 'production'
}


================================================
FILE: build/vue-loader.conf.js
================================================
'use strict'
const utils = require('./utils')
const config = require('../config')
const isProduction = process.env.NODE_ENV === 'production'

module.exports = {
  loaders: utils.cssLoaders({
    sourceMap: isProduction
      ? config.build.productionSourceMap
      : config.dev.cssSourceMap,
    extract: isProduction
  }),
  transformToRequire: {
    video: 'src',
    source: 'src',
    img: 'src',
    image: 'xlink:href'
  }
}


================================================
FILE: build/webpack.base.conf.js
================================================
'use strict'
const path = require('path')
const glob = require('glob')
const webpack = require('webpack')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

function getEntries () {
  const base = {
    'oj': ['./src/pages/oj/index.js'],
    'admin': ['./src/pages/admin/index.js']
  }
  if (process.env.USE_SENTRY === '1') {
    Object.keys(base).forEach(entry => {
      base[entry].push('./src/utils/sentry.js')
    })
  }
  return base
}

// get all entries
const entries = getEntries()
console.log("All entries: ")
Object.keys(entries).forEach(entry => {
  console.log(entry)
  entries[entry].forEach(ele => {
    console.log("- %s", ele)
  })
  console.log()
})

// prepare vendor asserts
const globOptions = {cwd: resolve('static/js')};
let vendorAssets = glob.sync('vendor.dll.*.js', globOptions);
vendorAssets = vendorAssets.map(file => 'static/js/' + file)


module.exports = {
  entry: entries,
  output: {
    path: config.build.assetsRoot,
    filename: '[name].js',
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  resolve: {
    modules: ['node_modules'],
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      '@oj': resolve('src/pages/oj'),
      '@admin': resolve('src/pages/admin'),
      '~': resolve('src/components')
    }
  },
  module: {
    rules: [
      // {
      //   test: /\.(js|vue)$/,
      //   loader: 'eslint-loader',
      //   enforce: 'pre',
      //   include: [resolve('src')],
      //   options: {
      //     formatter: require('eslint-friendly-formatter')
      //   }
      // },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader?cacheDirectory=true',
        exclude: /node_modules/,
        include: [resolve('src'), resolve('test')]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  },
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('./vendor-manifest.json')
    }),
    new HtmlWebpackIncludeAssetsPlugin({
      assets: [vendorAssets[0]],
      files: ['index.html', 'admin/index.html'],
      append: false
    }),
  ]
}


================================================
FILE: build/webpack.dev.conf.js
================================================
'use strict'
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')

// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})

module.exports = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  },
  // cheap-module-eval-source-map is faster for development
  devtool: '#cheap-module-eval-source-map',
  plugins: [
    new webpack.DefinePlugin({
      'process.env': config.dev.env
    }),
    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),

    // https://github.com/ampedandwired/html-webpack-plugin
    new HtmlWebpackPlugin({
      filename: config.build.ojIndex,
      template: config.build.ojTemplate,
      chunks: ['oj'],
      inject: true
    }),
    new HtmlWebpackPlugin({
      filename: config.build.adminIndex,
      template: config.build.adminTemplate,
      chunks: ['admin'],
      inject: true
    }),
    new FriendlyErrorsPlugin()
  ]
})


================================================
FILE: build/webpack.dll.conf.js
================================================
const webpack = require('webpack');
const path = require('path');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const config = require('../config')
const utils = require('./utils')
const glob = require('glob')
const fs = require('fs')

function resolve (dir) {
  return path.join(__dirname, '..', dir)
}

const NODE_ENV = utils.getNodeEnv()

const vendors = [
  'vue/dist/vue.esm.js',
  'vue-router',
  'vuex',
  'axios',
  'moment',
  'raven-js',
  'browser-detect'
];

// clear old dll
const globOptions = {cwd: resolve('static/js'), absolute: true};
let oldDlls = glob.sync('vendor.dll.*.js', globOptions);
console.log("cleaning old dll..")
oldDlls.forEach(f => {
  fs.unlink(f, _ => {})
})
console.log("building ..")

module.exports = {
  entry: {
    "vendor": vendors,
  },
  output: {
    path: path.join(__dirname, '../static/js'),
    filename: '[name].dll.[hash:7].js',
    library: '[name]_[hash]_dll',
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': NODE_ENV === 'production' ? config.build.env : config.dev.env
    }),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
    new UglifyJSPlugin({
      exclude: /\.min\.js$/,
      cache: true,
      parallel: true
    }),
    new webpack.DllPlugin({
      context: __dirname,
      path: path.join(__dirname, '[name]-manifest.json'),
      name: '[name]_[hash]_dll',
    })
  ]
};


================================================
FILE: build/webpack.prod.conf.js
================================================
'use strict'
const os = require('os');
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')

const webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  devtool: config.build.productionSourceMap ? '#hidden-source-map' : false,
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  },
  plugins: [
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
    new webpack.DefinePlugin({
      'process.env': config.build.env
    }),
    new webpack.optimize.ModuleConcatenationPlugin(),

    // extract css into its own file
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),
      allChunks: true
    }),
    // Compress extracted CSS. We are using this plugin so that possible
    // duplicated CSS from different components can be deduped.
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true
      }
    }),
    new UglifyJSPlugin({
      exclude: /\.min\.js$/,
      cache: true,
      parallel: true,
      sourceMap: true
    }),

    // keep module.id stable when vender modules does not change
    new webpack.HashedModuleIdsPlugin(),
    // split vendor js into its own file
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      chunks: ['oj', 'admin'],
      minChunks: 2
      // minChunks: function (module) {
      // any required modules inside node_modules are extracted to vendor
      // return (
      //   module.resource &&
      //   /\.js$/.test(module.resource) &&
      //   module.resource.indexOf(
      //     path.join(__dirname, '../node_modules')
      //   ) === 0
      // )
      // }
    }),
    // extract webpack runtime and module manifest to its own file in order to
    // prevent vendor hash from being updated whenever app bundle is updated
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest',
      chunks: ['vendor']
    }),
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ]),
    // generate dist index.html with correct asset hash for caching.
    // you can customize output by editing /index.html
    // see https://github.com/ampedandwired/html-webpack-plugin
    // oj
    new HtmlWebpackPlugin({
      filename: config.build.ojIndex,
      template: config.build.ojTemplate,
      chunks: ['manifest', 'vendor', 'oj'],
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      }
    }),
    // admin
    new HtmlWebpackPlugin({
      filename: config.build.adminIndex,
      template: config.build.adminTemplate,
      chunks: ['manifest', 'vendor', 'admin'],
      inject: true,
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeAttributeQuotes: true
        // more options:
        // https://github.com/kangax/html-minifier#options-quick-reference
      }
    })
  ]
})

if (config.build.productionGzip) {
  const CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig


================================================
FILE: config/dev.env.js
================================================
let date = require('moment')().format('YYYYMMDD')
let commit = require('child_process').execSync('git rev-parse HEAD').toString().slice(0, 5)
let version = `"${date}-${commit}"`

console.log(`current version is ${version}`)

module.exports = {
  NODE_ENV: '"development"',
  VERSION: version,
  USE_SENTRY: '0'
}


================================================
FILE: config/index.js
================================================
'use strict'
// Template version: 1.1.1
// see http://vuejs-templates.github.io/webpack for documentation.

const path = require('path')
const commonProxy = {
  onProxyReq: (proxyReq, req, res) => {
    proxyReq.setHeader('Referer', process.env.TARGET)
  },
  target: process.env.TARGET,
  changeOrigin: true
}

module.exports = {
  build: {
    env: require('./prod.env'),
    ojIndex: path.resolve(__dirname, '../dist/index.html'),
    ojTemplate: path.resolve(__dirname, '../src/pages/oj/index.html'),
    adminIndex: path.resolve(__dirname, '../dist/admin/index.html'),
    adminTemplate: path.resolve(__dirname, '../src/pages/admin/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '/__STATIC_CDN_HOST__/',
    productionSourceMap: process.env.USE_SENTRY === '1',
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: process.env.PORT || 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {
      "/api": commonProxy,
      "/public": commonProxy
    },
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}


================================================
FILE: config/prod.env.js
================================================
const merge = require('webpack-merge')
const devEnv = require('./dev.env')

module.exports = merge(devEnv, {
  NODE_ENV: '"production"',
})


================================================
FILE: deploy/Dockerfile
================================================
FROM node:6.11-alpine

RUN apk add --no-cache nginx git python build-base

VOLUME [ "/OJ_FE", "/var/log/nginx/", "/data/avatar"]
EXPOSE 80

CMD ["/bin/sh", "/OJ_FE/deploy/run.sh"]


================================================
FILE: deploy/nginx.conf
================================================
user nginx;

# Set number of worker processes automatically based on number of CPU cores.
worker_processes auto;

# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;

# Configures default error logger.
error_log /var/log/nginx/nginx_error.log warn;

daemon off;

# set pid path
pid  /tmp/nginx.pid;

# Includes files with directives to load dynamic modules.
include /etc/nginx/modules/*.conf;


events {
	# The maximum number of simultaneous connections that can be opened by
	# a worker process.
	worker_connections 1024;
}

http {
	# Includes mapping of file name extensions to MIME types of responses
	# and defines the default type.
	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	# Name servers used to resolve names of upstream servers into addresses.
	# It's also needed when using tcpsocket and udpsocket in Lua modules.
	#resolver 208.67.222.222 208.67.220.220;

	# Don't tell nginx version to clients.
	server_tokens off;

	# Specifies the maximum accepted body size of a client request, as
	# indicated by the request header Content-Length. If the stated content
	# length is greater than this size, then the client receives the HTTP
	# error code 413. Set to 0 to disable.
	client_max_body_size 100m;

	# Timeout for keep-alive connections. Server will close connections after
	# this time.
	keepalive_timeout 10;

	# Sendfile copies data between one FD and other from within the kernel,
	# which is more efficient than read() + write().
	sendfile on;

	# Don't buffer data-sends (disable Nagle algorithm).
	# Good for sending frequent small bursts of data in real time.
	tcp_nodelay on;

	# Causes nginx to attempt to send its HTTP response head in one packet,
	# instead of using partial frames.
	#tcp_nopush on;


	# Path of the file with Diffie-Hellman parameters for EDH ciphers.
	#ssl_dhparam /etc/ssl/nginx/dh2048.pem;

	# Specifies that our cipher suits should be preferred over client ciphers.
	ssl_prefer_server_ciphers on;

	# Enables a shared SSL cache with size that can hold around 8000 sessions.
	ssl_session_cache shared:SSL:2m;


	# Enable gzipping of responses.
	gzip on;
	gzip_types application/javascript text/css;

	# Set the Vary HTTP header as defined in the RFC 2616.
	gzip_vary on;

	# Enable checking the existence of precompressed files.
	#gzip_static on;


	# Specifies the main log format.
	log_format main '$remote_addr - $remote_user [$time_local] "$request" '
			'$status $body_bytes_sent "$http_referer" '
			'"$http_user_agent" "$http_x_forwarded_for"';

	# Sets the path, format, and configuration for a buffered log write.
	# access_log /var/log/nginx/access.log main;
	access_log off;

	server {
        listen 80 default_server;
        server_name _;

        location /public {
            root /app/data;
        }
        location /api {
            proxy_pass http://oj-backend:8080;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $http_host;
            client_max_body_size 200M;
        }
        location /admin {
            root /app/dist/admin;
            try_files $uri $uri/ /index.html =404;
        }
        location / {
            root /app/dist;
            try_files $uri $uri/ /index.html =404;
        }
    }
}


================================================
FILE: deploy/run.sh
================================================
#!/bin/sh

base=/OJ_FE

build_vendor_dll()
{
  if [ ! -e "${base}/build/vendor-manifest.json" ]
  then
      npm run build:dll
  fi
}
cd $base
npm install --registry=https://registry.npm.taobao.org && \
build_vendor_dll && \
npm run build

if [ $? -ne 0 ]; then
    echo "Build error, please check node version and package.json"
    exit 1
fi

exec nginx -c /OJ_FE/deploy/nginx.conf


================================================
FILE: deploy/sentry_release.sh
================================================
#!/bin/bash

DATE=`date +%Y%m%d`
COMMIT=`git rev-parse HEAD`
VERSION="$DATE-${COMMIT:0:5}"

echo "Current version is $VERSION"

if [ ! -z $USE_SENTRY ] && [ $USE_SENTRY == '1' ]; then

# create new release according to `VERSION`
docker run --rm -it -v $(pwd):/work getsentry/sentry-cli \
   sentry-cli --auth-token $SENTRY_AUTH_TOKEN releases -o onlinejudge -p onlinejudgefe new $VERSION

# upload js and source_maps
docker run --rm -it -v $(pwd):/work getsentry/sentry-cli \
   sentry-cli --auth-token $SENTRY_AUTH_TOKEN releases -o onlinejudge -p onlinejudgefe files $VERSION upload-sourcemaps ./dist/static/js
fi


================================================
FILE: package.json
================================================
{
  "name": "onlinejudge",
  "version": "2.7.6",
  "description": "onlinejudge front end",
  "author": "zemal <rawidn@163.com>",
  "private": true,
  "scripts": {
    "dev": "node build/dev-server.js",
    "start": "npm run dev",
    "build": "node build/build.js",
    "build:dll": "webpack --config=build/webpack.dll.conf.js",
    "lint": "eslint --ext .js,.vue src"
  },
  "dependencies": {
    "autoprefixer": "^7.1.2",
    "axios": "^0.18.0",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-2": "^6.22.0",
    "browser-detect": "^0.2.27",
    "chalk": "^2.4.1",
    "copy-webpack-plugin": "^4.5.1",
    "css-loader": "^0.28.11",
    "echarts": "^3.8.5",
    "element-ui": "^2.3.7",
    "extract-text-webpack-plugin": "^3.0.0",
    "file-loader": "^1.1.11",
    "font-awesome": "^4.7.0",
    "glob": "^7.1.2",
    "highlight.js": "^9.12.0",
    "html-webpack-include-assets-plugin": "^1.0.4",
    "html-webpack-plugin": "^2.30.1",
    "iview": "^2.13.0",
    "katex": "^0.10.0",
    "less": "^3.8.1",
    "less-loader": "^4.1.0",
    "moment": "^2.22.1",
    "optimize-css-assets-webpack-plugin": "^3.2.0",
    "ora": "^1.2.0",
    "papaparse": "^4.4.0",
    "raven-js": "^3.25.0",
    "screenfull": "^3.3.2",
    "shelljs": "^0.8.2",
    "tar-simditor": "^3.0.5",
    "tar-simditor-markdown": "^1.2.3",
    "uglifyjs-webpack-plugin": "^1.2.5",
    "url-loader": "^0.5.8",
    "vue": "^2.5.16",
    "vue-analytics": "^5.10.4",
    "vue-clipboard2": "^0.2.1",
    "vue-codemirror-lite": "^1.0.4",
    "vue-cropper": "^0.4.6",
    "vue-echarts": "^2.6.0",
    "vue-i18n": "^7.7.0",
    "vue-loader": "^13.3.0",
    "vue-router": "^3.0.1",
    "vue-style-loader": "^3.0.1",
    "vue-template-compiler": "^2.5.16",
    "vuex": "^3.0.1",
    "vuex-router-sync": "^5.0.0",
    "webpack": "^3.6.0",
    "webpack-merge": "^4.1.2"
  },
  "devDependencies": {
    "babel-eslint": "^7.1.1",
    "babel-register": "^6.22.0",
    "connect-history-api-fallback": "^1.3.0",
    "eslint": "^3.19.0",
    "eslint-config-standard": "^10.2.1",
    "eslint-friendly-formatter": "^3.0.0",
    "eslint-loader": "^1.7.1",
    "eslint-plugin-html": "^3.0.0",
    "eslint-plugin-import": "^2.11.0",
    "eslint-plugin-node": "^5.2.0",
    "eslint-plugin-promise": "^3.7.0",
    "eslint-plugin-standard": "^3.1.0",
    "eventsource-polyfill": "^0.9.6",
    "express": "^4.16.3",
    "friendly-errors-webpack-plugin": "^1.7.0",
    "http-proxy-middleware": "^0.18.0",
    "opn": "^5.3.0",
    "portfinder": "^1.0.13",
    "rimraf": "^2.6.0",
    "semver": "^5.5.0",
    "webpack-bundle-analyzer": "^2.11.2",
    "webpack-dev-middleware": "^1.12.0",
    "webpack-hot-middleware": "^2.22.1"
  },
  "engines": {
    "node": ">= 4.0.0",
    "npm": ">= 3.0.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}


================================================
FILE: src/i18n/admin/en-US.js
================================================
export const m = {
  // SideMenu.vue
  Dashboard: 'Dashboard',
  General: 'General',
  User: 'User',
  Announcement: 'Announcement',
  System_Config: 'System Config',
  Judge_Server: 'Judge Server',
  Prune_Test_Case: 'Prune Test Case',
  Problem: 'Problem',
  FromFile: 'From File',
  ToFile: 'To File',
  ShareSubmission: 'Share Submission',
  Problem_List: 'Problem List',
  Create_Problem: 'Create Problem',
  Export_Import_Problem: 'Export Or Import Problem',
  Contest: 'Contest',
  Contest_List: 'Contest List',
  Create_Contest: 'Create Contest',
  // User.vue
  User_User: 'User',
  Import_User: 'Import User',
  Generate_User: 'Generate User',
  // User.vue-dialog
  User_Info: 'User',
  User_Username: 'Username',
  User_Real_Name: 'Real Name',
  User_Email: 'Email',
  User_New_Password: 'New Password',
  User_Type: 'User Type',
  Problem_Permission: 'Problem Permission',
  Two_Factor_Auth: 'Two Factor Auth',
  Is_Disabled: 'Is Disabled',
  // Announcement.vue
  General_Announcement: 'Announcement',
  Announcement_Title: 'Title',
  Announcement_Content: 'Content',
  Announcement_visible: 'Visible',
  // Conf.vue
  SMTP_Config: 'SMTP Config',
  Server: 'Server',
  Port: 'Port',
  Email: 'Email',
  Password: 'Password',
  Website_Config: 'Web Config',
  Base_Url: 'Base Url',
  Name: 'Name',
  Shortcut: 'Shortcut',
  Footer: 'Footer',
  Allow_Register: 'Allow Register',
  Submission_List_Show_All: 'Submission List Show All',
  // JudgeServer.vue
  Judge_Server_Token: 'Judge Server Token',
  Judge_Server_Info: 'Judge Server',
  IP: 'IP',
  Judger_Version: 'Judger Version',
  Service_URL: 'Service URL',
  Last_Heartbeat: 'Last Heartbeat',
  Create_Time: 'Create Time',
  // PruneTestCase
  Test_Case_Prune_Test_Case: 'Prune Test Case',
  // Problem.vue
  Display_ID: 'Display ID',
  Title: 'Title',
  Description: 'Description',
  Input_Description: 'Input Description',
  Output_Description: 'Output Description',
  Time_Limit: 'Time Limit',
  Memory_limit: 'Memory limit',
  Difficulty: 'Difficulty',
  Visible: 'Visible',
  Languages: 'Languages',
  Input_Samples: 'Input Samples',
  Output_Samples: 'Output Samples',
  Add_Sample: 'Add Sample',
  Code_Template: 'Code_Template',
  Special_Judge: 'Special Judge',
  Use_Special_Judge: 'Use Special Judge',
  Special_Judge_Code: 'Special Judge Code',
  SPJ_language: 'SPJ language',
  Compile: 'Compile',
  TestCase: 'TestCase',
  IOMode: 'IO Mode',
  InputFileName: 'Input File Name',
  OutputFileName: 'Output File Name',
  Type: 'Type',
  Input: 'Input',
  Output: 'Output',
  Score: 'Score',
  Hint: 'Hint',
  Source: 'Source',
  Edit_Problem: 'Edit Problme',
  Add_Problme: 'Add Problem',
  High: 'High',
  Mid: 'Mid',
  Low: 'Low',
  Tag: 'Tag',
  New_Tag: 'New Tag',
   // ProblemList.vue
  Contest_Problem_List: 'Contest Problem List',
  // Contest.vue
  ContestTitle: 'Title',
  ContestDescription: 'Description',
  Contest_Start_Time: 'Start Time',
  Contest_End_Time: 'End Time',
  Contest_Password: 'Password',
  Contest_Rule_Type: 'Contest Rule Type',
  Real_Time_Rank: 'Real Time Rank',
  Contest_Status: 'Status',
  Allowed_IP_Ranges: 'Allowed IP Ranges',
  CIDR_Network: 'CIDR Network',
  // Dashboard.vue
  Last_Login: 'Last Login',
  System_Overview: 'System Overview',
  DashBoardJudge_Server: 'Judge Server',
  HTTPS_Status: 'HTTPS Status',
  Force_HTTPS: 'Force HTTPS',
  CDN_HOST: 'CDN HOST',
  // Login.vue
  Welcome_to_Login: 'Welcome to Login',
  GO: 'GO',
  username: 'username',
  password: 'password'
}


================================================
FILE: src/i18n/admin/zh-CN.js
================================================
export const m = {
  // SideMenu.vue
  Dashboard: '仪表盘',
  General: '常用设置',
  User: '用户管理',
  Announcement: '公告管理',
  System_Config: '系统配置',
  Judge_Server: '判题服务器',
  Prune_Test_Case: '测试用例',
  Problem: '问题',
  FromFile: '读取文件',
  ToFile: '写入文件',
  ShareSubmission: '分享提交',
  Problem_List: '问题列表',
  Create_Problem: '增加题目',
  Export_Import_Problem: '导入导出题目',
  Contest: '比赛&练习',
  Contest_List: '比赛列表',
  Create_Contest: '创建比赛',
  // User.vue
  User_User: '用户',
  Import_User: '导入用户',
  Generate_User: '生成用户',
  // User.vue-dialog
  User_Info: '用户信息',
  User_Username: '用户名',
  User_Real_Name: '真实姓名',
  User_Email: '用户邮箱',
  User_New_Password: '用户密码',
  User_Type: '用户类型',
  Problem_Permission: '问题权限',
  Two_Factor_Auth: '双因素认证',
  Is_Disabled: '是否禁用',
  // Announcement.vue
  General_Announcement: '公告',
  Announcement_Title: '标题',
  Announcement_Content: '内容',
  Announcement_visible: '是否可见',
  // Conf.vue
  SMTP_Config: 'SMTP 设置',
  Server: '服务器',
  Port: '端口',
  Email: '邮箱',
  Password: '授权码',
  Website_Config: '网站设置',
  Base_Url: '基础 Url',
  Name: '名称',
  Shortcut: '简称',
  Footer: '页脚',
  Allow_Register: '是否允许注册',
  Submission_List_Show_All: '显示全部题目的提交',
  // JudgeServer.vue
  Judge_Server_Token: '判题服务器接口',
  Judge_Server_Info: '判题服务器',
  IP: 'IP',
  Judger_Version: '判题机版本',
  Service_URL: '服务器 URL',
  Last_Heartbeat: '上一次心跳',
  Create_Time: '创建时间',
  // PruneTestCase
  Test_Case_Prune_Test_Case: '精简测试用例',
  // Problem.vue
  Display_ID: '显示 ID',
  Title: '题目',
  Description: '描述',
  Input_Description: '输入描述',
  Output_Description: '输出描述',
  Time_Limit: '时间限制',
  Memory_limit: '内存限制',
  Difficulty: '难度',
  Visible: '是否可见',
  Languages: '可选编程语言',
  Input_Samples: '输入样例',
  Output_Samples: '输出样例',
  Add_Sample: '添加样例',
  Code_Template: '代码模板',
  Special_Judge: 'Special Judge',
  Use_Special_Judge: '使用 Special Judge',
  Special_Judge_Code: 'Special Judge 代码',
  SPJ_language: 'SPJ 语言',
  Compile: '编译',
  TestCase: '测试用例',
  IOMode: 'IO 类型',
  InputFileName: '输入文件名',
  OutputFileName: '输出文件名',
  Type: '测试类型',
  Input: '输入',
  Output: '输出',
  Score: '分数',
  Hint: '提示',
  Source: '来源',
  Edit_Problem: '编辑问题',
  Add_Problem: '添加问题',
  High: '高',
  Mid: '中',
  Low: '低',
  Tag: '标签',
  New_Tag: '新增标签',
  // ProblemList.vue
  Contest_Problem_List: '比赛问题列表',
  // Contest.vue
  ContestTitle: '标题',
  ContestDescription: '描述',
  Contest_Start_Time: '开始时间',
  Contest_End_Time: '结束时间',
  Contest_Password: '密码',
  Contest_Rule_Type: '规则',
  Real_Time_Rank: '实时排名',
  Contest_Status: '状态',
  Allowed_IP_Ranges: '允许的 IP 范围',
  CIDR_Network: 'CIDR 网络',
  // Dashboard.vue
  Last_Login: '最后登录状态',
  System_Overview: '系统状况',
  DashBoardJudge_Server: '判题服务器',
  HTTPS_Status: 'HTTPS 状态',
  Force_HTTPS: '强制使用 HTTPS',
  CDN_HOST: 'CDN 主机',
  // Login.vue
  Welcome_to_Login: '欢迎登录 OnlineJudge 后台管理系统',
  GO: '登录',
  username: '用户名',
  password: '密码'
}


================================================
FILE: src/i18n/admin/zh-TW.js
================================================
export const m = {
  // SideMenu.vue
  Dashboard: '儀表板',
  General: '基本設定',
  User: '使用者管理',
  Announcement: '公告管理',
  System_Config: '系統設定',
  Judge_Server: 'Judge 伺服器',
  Prune_Test_Case: '測資',
  Problem: '試題',
  FromFile: '讀取檔案',
  ToFile: '寫入檔案',
  ShareSubmission: '分享提交',
  Problem_List: '試題列表',
  Create_Problem: '增加題目',
  Export_Import_Problem: '匯入匯出題目',
  Contest: '比賽',
  Contest_List: '比賽列表',
  Create_Contest: '建立比賽',
  // User.vue
  User_User: '使用者',
  Import_User: '匯入使用者',
  Generate_User: '生成使用者',
  // User.vue-dialog
  User_Info: '使用者資訊',
  User_Username: '使用者名稱',
  User_Real_Name: '真實姓名',
  User_Email: '使用者 E-mail',
  User_New_Password: '使用者密碼',
  User_Type: '帳號類型',
  Problem_Permission: '試題權限',
  Two_Factor_Auth: '兩步驟驗證',
  Is_Disabled: '是否禁用',
  // Announcement.vue
  General_Announcement: '公告',
  Announcement_Title: '標題',
  Announcement_Content: '內容',
  Announcement_visible: '是否可見',
  // Conf.vue
  SMTP_Config: 'SMTP 設定',
  Server: '伺服器',
  Port: '連接埠',
  Email: 'E-mail',
  Password: '密碼',
  Website_Config: '網站設定',
  Base_Url: 'Base Url',
  Name: '名稱',
  Shortcut: '簡稱',
  Footer: '頁尾',
  Allow_Register: '是否允許註冊',
  Submission_List_Show_All: '顯示全部題目的提交',
  // JudgeServer.vue
  Judge_Server_Token: 'Judge 伺服器 Token',
  Judge_Server_Info: 'Judge 伺服器',
  IP: 'IP',
  Judger_Version: 'Judge 版本',
  Service_URL: '伺服器 URL',
  Last_Heartbeat: '上一次活動訊號',
  Create_Time: '建立時間',
  // PruneTestCase
  Test_Case_Prune_Test_Case: '精簡測資',
  // Problem.vue
  Display_ID: '顯示 ID',
  Title: '題目',
  Description: '描述',
  Input_Description: '輸入描述',
  Output_Description: '輸出描述',
  Time_Limit: '時間限制',
  Memory_limit: '記憶體限制',
  Difficulty: '難度',
  Visible: '是否可見',
  Languages: '可選程式語言',
  Input_Samples: '輸入範例',
  Output_Samples: '輸出範例',
  Add_Sample: '加入範例',
  Code_Template: '程式碼模板',
  Special_Judge: 'Special Judge',
  Use_Special_Judge: '使用 Special Judge',
  Special_Judge_Code: 'Special Judge Code',
  SPJ_language: 'SPJ language',
  Compile: '編譯',
  TestCase: '測資',
  IOMode: 'IO 類型',
  InputFileName: '輸入檔名',
  OutputFileName: '輸出檔名',
  Type: '測試類型',
  Input: '輸入',
  Output: '輸出',
  Score: '分數',
  Hint: '提示',
  Source: '來源',
  // Contest.vue
  ContestTitle: '標題',
  ContestDescription: '描述',
  Contest_Start_Time: '開始時間',
  Contest_End_Time: '結束時間',
  Contest_Password: '密碼',
  Contest_Rule_Type: '規則',
  Real_Time_Rank: '即時排名',
  Contest_Status: '狀態',
  Allowed_IP_Ranges: '允許的 IP 範圍',
  CIDR_Network: 'CIDR Network',
  // Dashboard.vue
  Last_Login: '最後登入狀態',
  System_Overview: '系統狀況',
  DashBoardJudge_Server: 'Judge 伺服器',
  HTTPS_Status: 'HTTPS 狀態',
  Force_HTTPS: '強制 HTTPS',
  CDN_HOST: 'CDN HOST'
}


================================================
FILE: src/i18n/index.js
================================================
import Vue from 'vue'
import VueI18n from 'vue-i18n'
// ivew UI
import ivenUS from 'iview/dist/locale/en-US'
import ivzhCN from 'iview/dist/locale/zh-CN'
import ivzhTW from 'iview/dist/locale/zh-TW'
// element UI
import elenUS from 'element-ui/lib/locale/lang/en'
import elzhCN from 'element-ui/lib/locale/lang/zh-CN'
import elzhTW from 'element-ui/lib/locale/lang/zh-TW'

Vue.use(VueI18n)

const languages = [
  {value: 'en-US', label: 'English', iv: ivenUS, el: elenUS},
  {value: 'zh-CN', label: '简体中文', iv: ivzhCN, el: elzhCN},
  {value: 'zh-TW', label: '繁體中文', iv: ivzhTW, el: elzhTW}
]
const messages = {}

// combine admin and oj
for (let lang of languages) {
  let locale = lang.value
  let m = require(`./oj/${locale}`).m
  Object.assign(m, require(`./admin/${locale}`).m)
  let ui = Object.assign(lang.iv, lang.el)
  messages[locale] = Object.assign({m: m}, ui)
}
// load language packages
export default new VueI18n({
  locale: 'en-US',
  messages: messages
})

export {languages}


================================================
FILE: src/i18n/oj/en-US.js
================================================
export const m = {
  // 404.vue
  Go_Home: 'Go Home',
  // Problem.vue
  Description: 'Description',
  Input: 'Input',
  Output: 'Output',
  Sample_Input: 'Sample Input',
  Sample_Output: 'Sample Output',
  Hint: 'Hint',
  Source: 'Source',
  Status: 'Status',
  Information: 'Information',
  Time_Limit: 'Time Limit',
  Memory_Limit: 'Memory Limit',
  Created: 'Created By',
  Level: 'Level',
  Score: 'Score',
  Tags: 'Tags',
  Show: 'Show',
  Submit: 'Submit',
  Submitting: 'Submitting',
  Judging: 'Judging',
  Wrong_Answer: 'Wrong Answer',
  Statistic: 'Statistic',
  Close: 'Close',
  View_Contest: 'View Contest',
  Are_you_sure_you_want_to_reset_your_code: 'Are you sure you want to reset your code?',
  Code_can_not_be_empty: 'Code can not be empty',
  Submit_code_successfully: 'Submit code successfully',
  You_have_solved_the_problem: 'You have solved the problem',
  Submitted_successfully: 'Submitted successfully',
  You_have_submitted_a_solution: 'You have submitted a solution.',
  Contest_has_ended: 'Contest has ended',
  You_have_submission_in_this_problem_sure_to_cover_it: 'You have submission in this problem, sure to cover it?',
  // About.vue
  Compiler: 'Compiler',
  Result_Explanation: 'Result Explanation',
  Pending_Judging_Description: 'You solution will be judged soon, please wait for result.',
  Compile_Error_Description: "Failed to compile your source code. Click on the link to see compiler's output.",
  Accepted_Description: 'Congratulations. Your solution is correct.',
  Wrong_Answer_Description: "Your program's output doesn't match judger's answer.",
  Runtime_Error_Description: 'Your program terminated abnormally. Possible reasons are: segment fault, divided by zero or exited with code other than 0.',
  Time_Limit_Exceeded_Description: 'The CPU time your program used has exceeded limit.',
  Memory_Limit_Exceeded_Description: 'The memory your program actually used has exceeded limit.',
  System_Error_Description: 'Oops, something has gone wrong with the judger. Please report this to administrator.',
  // ACMContestRank.vue
  Menu: 'Menu',
  Chart: 'Chart',
  Auto_Refresh: 'Auto Refresh',
  RealName: 'RealName',
  Force_Update: 'Force Update',
  download_csv: 'download csv',
  TotalTime: 'TotalTime',
  Top_10_Teams: 'Top 10 Teams',
  save_as_image: 'save as image',
  // ACMHelper.vue
  ACM_Helper: 'ACM Helper',
  AC_Time: 'AC Time',
  ProblemID: 'ProblemID',
  First_Blood: 'First Blood',
  Username: 'Username',
  Checked: 'Checked',
  Not_Checked: 'Not Checked',
  Check_It: 'Check It',
  // ACMRank.vue
  ACM_Ranklist: 'ACM Ranklist',
  mood: 'mood',
  AC: 'AC',
  Rating: 'Rating',
  // Announcements.vue
  Contest_Announcements: 'Contest Announcements',
  By: 'By',
  // ApplyResetPassword.vue
  The_email_doesnt_exist: 'The email doesn\'t exist',
  Success: 'Success',
  Password_reset_mail_sent: 'Password reset mail has been sent to your email,',
  // FAQ.vue
  Frequently_Asked_Questions: 'Frequently Asked Questions',
  Where_is_the_input_and_the_output: 'Where is the input and the output?',
  Where_is_the_input_and_the_output_answer_part_1: 'Your program shall read input from',
  Standard_Input: 'Standard Input',
  Where_is_the_input_and_the_output_answer_part_3: 'and write output to',
  Standard_Output: 'Standard Output',
  Where_is_the_input_and_the_output_answer_part_5: 'For example,you can use',
  Where_is_the_input_and_the_output_answer_part_6: 'in C or',
  Where_is_the_input_and_the_output_answer_part_7: 'in C++ to read from stdin,and use',
  Where_is_the_input_and_the_output_answer_part_8: 'in C or',
  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',
  What_is_the_meaning_of_submission_execution_time: 'What\'s the meaning of the submission execution time?',
  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.',
  How_Can_I_use_CPP_Int64: 'How can I use C++ Int64?',
  How_Can_I_use_CPP_Int64_answer_part_1: 'You should declare as',
  How_Can_I_use_CPP_Int64_answer_part_2: 'and use with',
  or: 'or',
  using: 'using',
  How_Can_I_use_CPP_Int64_answer_part_3: 'will result in',
  Java_specifications: 'Java specifications?',
  Java_specifications_answer_part_1: 'All programs must begin in a static main method in a',
  Java_specifications_answer_part_2: 'class. Do not use public classes: even',
  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.',
  About_presentation_error: 'About presentation error?',
  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',
  last: 'last',
  About_presentation_error_answer_part_2: 'line.  if it\'s still different with the correct output, the result will be',
  How_to_report_bugs: 'How to report bugs about this oj?',
  How_to_report_bugs_answer_part_1: 'The onlinejudge is open source, you can open an issue in',
  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.',
  // Cancel.vue
  Cancel: 'Cancel',
  // ContestDetail.vue
  Problems: 'Problems',
  Announcements: 'Announcements',
  Submissions: 'Submissions',
  Rankings: 'Rankings',
  Overview: 'Overview',
  Admin_Helper: 'Admin Helper',
  StartAt: 'StartAt',
  EndAt: 'EndAt',
  ContestType: 'ContestType',
  Creator: 'Creator',
  Public: 'Public',
  Password_Protected: 'Password Protected',
  // ContestList.vue
  Rule: 'Rule',
  OI: 'OI',
  ACM: 'ACM',
  Underway: 'Underway',
  Not_Started: 'Not_Started',
  Ended: 'Ended',
  No_contest: 'No contest',
  Please_login_first: 'Please login first!',
  // ContestProblemList
  Problems_List: 'Problems List',
  No_Problems: 'No Problems',
  // CodeMirror.vue
  Language: 'Language',
  Theme: 'Theme',
  Reset_to_default_code_definition: 'Reset to default code definition',
  Upload_file: 'Upload file',
  Monokai: 'Monokai',
  Solarized_Light: 'Solarized Light',
  Material: 'Material',
  // KatexEditor.vue
  Latex_Editor: 'Latex Editor',
  // NavBar.vue
  Home: 'Home',
  NavProblems: 'Problems',
  Contests: 'Contests',
  NavStatus: 'Status',
  Rank: 'Rank',
  ACM_Rank: 'ACM Rank',
  OI_Rank: 'OI Rank',
  About: 'About',
  Judger: 'Judger',
  FAQ: 'FAQ',
  Login: 'Login',
  Register: 'Register',
  MyHome: 'Home',
  MySubmissions: 'Submissions',
  Settings: 'Settings',
  Management: 'Management',
  Logout: 'Logout',
  Welcome_to: 'Welcome to',
  // announcements.vue
  Refresh: 'Refresh',
  Back: 'Back',
  No_Announcements: 'No Announcements',
  // Setting.vue
  Profile: 'Profile',
  Account: 'Account',
  Security: 'Security',
  // AccoutSetting.vue
  ChangePassword: 'Change Password',
  ChangeEmail: 'Change Email',
  Update_Password: 'Update Password',
  // ProfileSetting.vue
  Avatar_Setting: 'Avatar Setting',
  Profile_Setting: 'Profile Setting',
  // SecuritySettig
  Sessions: 'Sessions',
  Two_Factor_Authentication: 'Two Factor Authentication',
  // Login.vue
  LoginUsername: 'Username',
  LoginPassword: 'Password',
  TFA_Code: 'Code from your TFA app',
  No_Account: 'No account? Register now!',
  Forget_Password: 'Forget Password',
  UserLogin: 'Login',
  Welcome_back: 'Welcome back to OJ',
  // OIRank.vue
  OI_Ranklist: 'OI Ranklist',
  // OIContestRank.vue
  Total_Score: 'Total Score',
  // ProblemList.vue
  Problem_List: 'Problem List',
  High: 'High',
  Mid: 'Mid',
  Low: 'Low',
  All: 'All',
  Reset: 'Reset',
  Pick_One: 'Pick one',
  Difficulty: 'Difficulty',
  Total: 'Total',
  AC_Rate: 'AC Rate',
  // Register.vue
  RegisterUsername: 'Username',
  Email_Address: 'Email Address',
  RegisterPassword: 'Password',
  Password_Again: 'Password Again',
  Captcha: 'Captcha',
  UserRegister: 'Register',
  Already_Registed: 'Already registed? Login now!',
  The_username_already_exists: 'The username already exists.',
  The_email_already_exists: 'The email already exists',
  password_does_not_match: 'password does not match',
  Thanks_for_registering: 'Thanks for your registering, you can login now',
  // ResetPassword.vue and ApplyResetPassword.vue
  Reset_Password: 'Lost Password',
  RPassword: 'Password',
  RPassword_Again: 'Password Again',
  RCaptcha: 'Captcha',
  ApplyEmail: 'Your Email Address',
  Send_Password_Reset_Email: 'Send Password Reset Email',
  Your_password_has_been_reset: 'Your password has been reset.',
  // Save.vue
  Save: 'Save',
  // Simditor.vue
  Uploading_is_in_progress: 'Uploading is in progress, are you sure to leave this page?',
  // SubmissionDetails.vue
  Lang: 'Lang',
  Share: 'Share',
  UnShare: 'UnShare',
  Succeeded: 'Succeeded',
  Real_Time: 'Real Time',
  Signal: 'Signal',
  // SubmissionList.vue
  When: 'When',
  ID: 'ID',
  Time: 'Time',
  Memory: 'Memory',
  Author: 'Author',
  Option: 'Option',
  Mine: 'Mine',
  Search_Author: 'Search Author',
  Accepted: 'Accepted',
  Time_Limit_Exceeded: 'Time Limit Exceeded',
  Memory_Limit_Exceeded: 'Memory Limit Exceeded',
  Runtime_Error: 'Runtime Error',
  System_Error: 'System Error',
  Pending: 'Pending',
  Partial_Accepted: 'Partial Accepted',
  Compile_Error: 'Compile Error',
  Rejudge: 'Rejudge',
  // UserHome.vue
  UserHomeSolved: 'Solved',
  UserHomeserSubmissions: 'Submissions',
  UserHomeScore: 'Score',
  List_Solved_Problems: 'List of solved problems',
  UserHomeIntro: 'The guy is so lazy that has not solved any problem yet.'
}


================================================
FILE: src/i18n/oj/zh-CN.js
================================================
export const m = {
    // 404.vue
  Go_Home: '返回主页',
  // Problem.vue
  Description: '题目描述',
  Input: '输入',
  Output: '输出',
  Sample_Input: '输入样例',
  Sample_Output: '输出样例',
  Hint: '提示',
  Source: '题目来源',
  Status: '状态',
  Information: '题目信息',
  Time_Limit: '时间限制',
  Memory_Limit: '内存限制',
  Created: '出题人',
  Level: '难度',
  Score: '分数',
  Tags: '标签',
  Show: '显示',
  Submit: '提交',
  Submitting: '正在提交',
  Judging: '正在评分',
  Wrong_Answer: '答案错误',
  Statistic: '统计',
  Close: '关闭',
  View_Contest: '查看比赛',
  Are_you_sure_you_want_to_reset_your_code: '确定要重置代码吗?',
  Code_can_not_be_empty: '不能提交空代码',
  Submit_code_successfully: '成功提交代码',
  You_have_solved_the_problem: '你已经解决了该问题',
  Submitted_successfully: '成功提交',
  You_have_submitted_a_solution: '你已经提交了解答',
  Contest_has_ended: '比赛已结束',
  You_have_submission_in_this_problem_sure_to_cover_it: '你已经提交了解答,确定要覆盖吗?',
  // About.vue
  Compiler: '编译器',
  Result_Explanation: '结果解释',
  Pending_Judging_Description: '您的解答将很快被测评,请等待结果。',
  Compile_Error_Description: '无法编译您的源代码,点击链接查看编译器的输出。',
  Accepted_Description: '你的解题方法是正确的。',
  Wrong_Answer_Description: '你的程序输出结果与判题程序的答案不符。',
  Runtime_Error_Description: '您的程序异常终止,可能的原因是:段错误,被零除或用非0的代码退出程序。',
  Time_Limit_Exceeded_Description: '您的程序使用的 CPU 时间已超出限制。',
  Memory_Limit_Exceeded_Description: '程序实际使用的内存已超出限制。',
  System_Error_Description: '糟糕,判题程序出了问题。请报告给管理员。',
  // ACMContestRank.vue
  Menu: '菜单',
  Chart: '图表',
  Auto_Refresh: '自动刷新',
  RealName: '真名',
  Force_Update: '强制刷新',
  download_csv: '下载 csv',
  TotalTime: '总时间',
  Top_10_Teams: '前 10 强队伍',
  save_as_image: '保存图片',
  // ACMHelper.vue
  ACM_Helper: 'ACM 助手',
  AC_Time: 'AC 时间',
  ProblemID: '问题 ID',
  First_Blood: '一血',
  Username: '用户名',
  Checked: '已检查',
  Not_Checked: '未检查',
  Check_It: '现在检查',
  // ACMRank.vue
  ACM_Ranklist: 'ACM 排名',
  mood: '格言',
  AC: 'AC',
  Rating: '评分',
  // Announcements.vue
  Contest_Announcements: '比赛公告',
  By: '创建人',
  // ApplyResetPassword.vue
  The_email_doesnt_exist: '该电子邮件地址不存在',
  Success: '成功',
  Password_reset_mail_sent: '密码重置邮件已发送。',
  // FAQ.vue
  Frequently_Asked_Questions: '常见问题',
  Where_is_the_input_and_the_output: '输入和输出在哪里?',
  Where_is_the_input_and_the_output_answer_part_1: '您的程序应从',
  Standard_Input: '标准输入',
  Where_is_the_input_and_the_output_answer_part_3: '读取输入,并将输出写入',
  Standard_Output: '标准输出',
  Where_is_the_input_and_the_output_answer_part_5: '例如,您可以在 C 中使用',
  Where_is_the_input_and_the_output_answer_part_6: '或在 C ++ 中使用',
  Where_is_the_input_and_the_output_answer_part_7: '读取,并在 C 中使用',
  Where_is_the_input_and_the_output_answer_part_8: '或在 C ++ 中使用',
  Where_is_the_input_and_the_output_answer_part_9: '写入 stdout。用户程序不允许读取或写入文件,否则您将收到',
  What_is_the_meaning_of_submission_execution_time: '提交执行时间是什么意思?',
  What_is_the_meaning_of_submission_execution_time_answer: 'OnlineJudge 可能会使用不同的输入文件多次测试您的代码。如果您的代码在每个输入文件的时间限制内给出正确的答案,则显示的执行时间是每个测试用例所花费的最大时间。否则,执行时间将毫无意义。',
  How_Can_I_use_CPP_Int64: '如何使用 C ++ Int64?',
  How_Can_I_use_CPP_Int64_answer_part_1: '您应该声明',
  How_Can_I_use_CPP_Int64_answer_part_2: '并与',
  or: '或',
  using: '一起使用,使用',
  How_Can_I_use_CPP_Int64_answer_part_3: ' 将导致',
  Java_specifications: 'Java 规范?',
  Java_specifications_answer_part_1: '所有程序都必须以',
  Java_specifications_answer_part_2: '类的静态 main 方法开始。不要使用公共类:即使',
  Java_specifications_answer_part_3: '也必须是非公共类以避免编译错误使用缓冲I / O以避免由于过度刷新而导致超出时间限制',
  About_presentation_error: '关于输出格式错误?',
  About_presentation_error_answer_part_1: '该 OJ 中没有输出格式错误。Judger将对自动对输出修整然后包裹在输出的',
  last: '最后',
  About_presentation_error_answer_part_2: '一行中。如果仍然与正确的输出不同,则结果将是',
  How_to_report_bugs: '如何报告有关此 OJ 的错误?',
  How_to_report_bugs_answer_part_1: 'onlinejudge 是开源的,您可以到',
  How_to_report_bugs_answer_part_2: '提交问题。需要有关错误的详细信息(例如env,版本..),这将帮助我们极大地解决该错误。当然,我们很高兴合并您的请求。',
  // Cancel.vue
  Cancel: '取消',
  // ContestDetail.vue
  Problems: '题目',
  Announcements: '公告',
  Submissions: '提交信息',
  Rankings: '排名',
  Overview: '概要',
  Admin_Helper: '管理员助手',
  StartAt: '开始时间',
  EndAt: '结束时间',
  ContestType: '比赛类型',
  Creator: '发起人',
  Public: '公开',
  Password_Protected: '密码保护',
  // ContestList.vue
  Rule: '赛制',
  OI: 'OI',
  ACM: 'ACM',
  Underway: '进行中',
  Not_Started: '筹备中',
  Ended: '已结束',
  No_contest: '尚无练习或比赛',
  Please_login_first: '请先登录!',
  // ContestProblemList.vue
  Problems_List: '问题列表',
  No_Problems: '尚无问题',
  // CodeMirror.vue
  Language: '语言',
  Theme: '主题',
  Reset_to_default_code_definition: '重设返回默认代码设置',
  Upload_file: '上传文件',
  Monokai: '物界',
  Solarized_Light: '日光灯',
  Material: '材料',
  // KatexEditor.vue
  Latex_Editor: 'Latex 编辑器',
  // NavBar.vue
  Home: '首页',
  NavProblems: '问题',
  Contests: '练习&比赛',
  NavStatus: '状态',
  Rank: '排名',
  ACM_Rank: 'ACM 排名',
  OI_Rank: 'OI 排名',
  About: '关于',
  Judger: '评分器',
  FAQ: '常见问题',
  Login: '登录',
  Register: '注册',
  MyHome: '我的主页',
  MySubmissions: '我的提交',
  Settings: '我的设置',
  Management: '后台管理',
  Logout: '退出',
  Welcome_to: '欢迎来到',
  // announcements.vue
  Refresh: '刷新',
  Back: '返回',
  No_Announcements: '暂无公告',
  // Setting.vue
  Profile: '个人信息设置',
  Account: '账号设置',
  Security: '安全设置',
  // AccoutSetting.vue
  ChangePassword: '更改密码',
  ChangeEmail: '更改邮箱',
  Update_Password: '更新密码',
  // ProfileSetting.vue
  Avatar_Setting: '头像设置',
  Profile_Setting: '个人信息设置',
  // SecuritySettig
  Sessions: '登录记录',
  Two_Factor_Authentication: '双因素认证',
  // Login.vue
  LoginUsername: '用户名',
  LoginPassword: '密码',
  TFA_Code: 'TFA App 双因素认证码',
  No_Account: '还没账号,立即注册!',
  Forget_Password: '忘记密码',
  UserLogin: '登录',
  Welcome_back: '欢迎回来',
  // OIRank.vue
  OI_Ranklist: 'OI 排名',
  // OIContestRank.vue
  Total_Score: '总分',
  // ProblemList.vue
  Problem_List: '问题列表',
  High: '高',
  Mid: '中',
  Low: '低',
  All: '全部',
  Reset: '重置',
  Pick_One: '选择',
  Difficulty: '难度',
  Total: '总数',
  AC_Rate: '通过率',
  // Register.vue
  RegisterUsername: '用户名',
  Email_Address: '电子邮箱',
  RegisterPassword: '密码',
  Password_Again: '确认密码',
  Captcha: '验证码',
  UserRegister: '注册',
  Already_Registed: '已经注册?现在登录!',
  The_username_already_exists: '该用户名已存在',
  The_email_already_exists: '该电子邮件地址已存在',
  password_does_not_match: '密码不匹配',
  Thanks_for_registering: '感谢注册,您现在可以登录了',
  // ResetPassword.vue and ApplyResetPassword.vue
  Reset_Password: '重置密码',
  RPassword: '密码',
  RPassword_Again: '确认密码',
  RCaptcha: '验证码',
  ApplyEmail: '电子邮箱',
  Send_Password_Reset_Email: '发送重置密码到邮箱',
  Your_password_has_been_reset: '您的密码已重置',
  // Save.vue
  Save: '保存',
  // Simditor.vue
  Uploading_is_in_progress: '正在上传,您确定要离开当前页面吗?',
  // SubmissionDetails.vue
  Lang: '语言',
  Share: '分享',
  UnShare: '不分享',
  Succeeded: '成功',
  Real_Time: '真实时间',
  Signal: '信号',
  // SubmissionList.vue
  When: '时间',
  ID: 'ID',
  Time: '时间',
  Memory: '内存',
  Author: '作者',
  Option: '选项',
  Mine: '我的',
  Search_Author: '搜索作者',
  Accepted: '答案正确',
  Time_Limit_Exceeded: '运行超时',
  Memory_Limit_Exceeded: '内存超限',
  Runtime_Error: '运行时错误',
  System_Error: '系统错误',
  Pending: '等待评分',
  Partial_Accepted: '部分正确',
  Compile_Error: '编译失败',
  Rejudge: '重新评分',
  // UserHome.vue
  UserHomeSolved: '已解决问题的数量',
  UserHomeserSubmissions: '提交次数',
  UserHomeScore: '分数',
  List_Solved_Problems: '已解决问题的列表',
  UserHomeIntro: '这个家伙太懒了,还没有做题呢...'
}


================================================
FILE: src/i18n/oj/zh-TW.js
================================================
export const m = {
  // 404.vue
  Go_Home: '回到首頁',
  // Problem.vue
  Description: '題目描述',
  Input: '輸入',
  Output: '輸出',
  Sample_Input: '輸入範例',
  Sample_Output: '輸出範例',
  Hint: '提示',
  Source: '題目來源',
  Status: '狀態',
  Information: '題目資訊',
  Time_Limit: '時間限制',
  Memory_Limit: '記憶體限制',
  Created: '出題者',
  Level: '難度',
  Score: '分數',
  Tags: '標籤',
  Show: '顯示',
  Submit: '提交',
  Submitting: '提交中',
  Judging: '評分中',
  Wrong_Answer: '答案錯誤',
  Statistic: '統計',
  Close: '關閉',
  View_Contest: '查看比賽',
  Are_you_sure_you_want_to_reset_your_code: '你確定要重置你的程式碼嗎?',
  Code_can_not_be_empty: '你不能提交空的程式碼',
  Submit_code_successfully: '成功提交程式碼',
  You_have_solved_the_problem: '你已經解決了該試題',
  Submitted_successfully: '成功提交',
  You_have_submitted_a_solution: '你已經提交了解答.',
  Contest_has_ended: '比賽已經結束',
  You_have_submission_in_this_problem_sure_to_cover_it: '你已經提交了解答,確定要覆蓋嗎?',
  // About.vue
  Compiler: '編譯器',
  Result_Explanation: '結果說明',
  Pending_Judging_Description: '您的答案即將進行評分,請等待結果。',
  Compile_Error_Description: '無法編譯您的原始碼,請點選連結以檢視編譯器的輸出。',
  Accepted_Description: '您的解題方法是正確的。',
  Wrong_Answer_Description: '您程式的輸出結果與標準程式的答案不符。',
  Runtime_Error_Description: '您的程式異常終止,可能的原因是: 記憶體區段錯誤、被零除或結束程式時傳回非 0 的值。',
  Time_Limit_Exceeded_Description: '您的程式使用的 CPU 時間已超出限制。',
  Memory_Limit_Exceeded_Description: '程式實際使用的記憶體已超出限制。',
  System_Error_Description: 'Judge 系統發生錯誤。請回報系統管理員。',
  // ACMContestRank.vue
  Menu: '選單',
  Chart: '圖表',
  Auto_Refresh: '自動重新載入',
  RealName: '真實名稱',
  Force_Update: '強制重新載入',
  download_csv: '下載csv檔',
  TotalTime: '總時間',
  Top_10_Teams: '前10名隊伍',
  save_as_image: '保存圖片',
  // ACMHelper.vue
  ACM_Helper: 'ACM助手',
  AC_Time: 'AC 時間',
  ProblemID: '題目ID',
  First_Blood: '頭香',
  Username: '使用者名稱',
  Checked: '已檢查',
  Not_Checked: '還未檢查',
  Check_It: '現在檢查',
  // ACMRank.vue
  ACM_Ranklist: 'ACM 排名',
  mood: '個人狀態',
  AC: 'AC',
  Rating: '評分',
  // Announcements.vue
  Contest_Announcements: '比賽公告',
  By: '創建者',
  // ApplyResetPassword.vue
  The_email_doesnt_exist: '此電子郵件並不存在',
  Success: '成功',
  Password_reset_mail_sent: '已發送重置密碼之電子郵件',
  // FAQ.vue
  Frequently_Asked_Questions: '常見問題',
  Where_is_the_input_and_the_output: '輸入與輸出在哪邊?',
  Where_is_the_input_and_the_output_answer_part_1: '你的程式會從',
  Standard_Input: '標準輸入',
  Where_is_the_input_and_the_output_answer_part_3: '讀取輸入,並且將結果輸出到',
  Standard_Output: '標準輸出',
  Where_is_the_input_and_the_output_answer_part_5: '例如,你可以在 C 中使用',
  Where_is_the_input_and_the_output_answer_part_6: '或在 C++ 中使用',
  Where_is_the_input_and_the_output_answer_part_7: '讀取輸入,並在 C 中使用',
  Where_is_the_input_and_the_output_answer_part_8: '或在 C++ 中使用',
  Where_is_the_input_and_the_output_answer_part_9: '來輸出到stdout。你的程式不被允許讀取或著是寫入檔案,否則你將獲得',
  What_is_the_meaning_of_submission_execution_time: '提交執行時間是什麼意思?',
  What_is_the_meaning_of_submission_execution_time_answer: '線上解題系統可能會使用不同的輸入檔案來多次測試你的程式碼。如果你的程式碼在每個輸入檔案的時間限制內給出正確的答案,則顯示的執行時間是每個測資所花費的最大時間。否則,執行時間將毫無意義',
  How_Can_I_use_CPP_Int64: '我要如何使用 C++ Int64?',
  How_Can_I_use_CPP_Int64_answer_part_1: '你應該宣告為',
  How_Can_I_use_CPP_Int64_answer_part_2: '並且與',
  or: '或',
  using: '一起使用,使用',
  How_Can_I_use_CPP_Int64_answer_part_3: '將導致',
  Java_specifications: 'Java 規範?',
  Java_specifications_answer_part_1: '所有的程式都必須以',
  Java_specifications_answer_part_2: '類別的靜態main方法開始執行。不要使用public類別:即使',
  Java_specifications_answer_part_3: '也必須是非public類別以避免編譯錯誤使用緩衝I/O以避免過度重載而導致超出時間限制',
  About_presentation_error: '關於輸出格式錯誤?',
  About_presentation_error_answer_part_1: '此OJ中沒有輸出格式錯誤。Judge系統將自動整理輸出然後包裹在輸出的',
  last: '最後',
  About_presentation_error_answer_part_2: '一行中。如果仍然與正確的輸出不同,則結果將是',
  How_to_report_bugs: '如何回報關於此OJ的錯誤?',
  How_to_report_bugs_answer_part_1: '此線上解題系統是開源的,你可以到',
  How_to_report_bugs_answer_part_2: '提交問題。請提供有關錯誤的詳細訊息(如env,版本...),這將極大地幫助我們解決該錯誤。當然,我們十分樂意合併你的請求',
  // Cancel.vue
  Cancel: '取消',
  // ContestDetail.vue
  Problems: '題目',
  Announcements: '公告',
  Submissions: '提交資訊',
  Rankings: '排名',
  Overview: '概要',
  Admin_Helper: '管理員助手',
  StartAt: '開始時間',
  EndAt: '結束時間',
  ContestType: '比賽類型',
  Creator: '發起人',
  Public: '公開',
  Password_Protected: '密碼保護',
  // ContestList.vue
  Rule: '賽制',
  OI: 'OI',
  ACM: 'ACM',
  Underway: '進行中',
  Not_Started: '準備中',
  Ended: '已結束',
  No_contest: '目前無任何比賽',
  Please_login_first: '請先登入!',
  // ContestProblemList.vue
  Problems_List: '試題列表',
  No_Problems: '暫無試題',
  // CodeMirror.vue
  Language: '語言',
  Theme: '主題',
  Reset_to_default_code_definition: '使用預設程式碼設定',
  Upload_file: '上傳文件',
  Monokai: 'Monokai',
  Solarized_Light: '日光燈',
  Material: '材料',
  // KatexEditor.vue
  Latex_Editor: 'Latex 編輯器',
  // NavBar.vue
  Home: '首頁',
  NavProblems: '試題',
  Contests: '比賽',
  NavStatus: '狀態',
  Rank: '排名',
  ACM_Rank: 'ACM 排名',
  OI_Rank: 'OI 排名',
  About: '關於',
  Judger: 'Judge 說明',
  FAQ: '常見問題',
  Login: '登入',
  Register: '註冊',
  MyHome: '我的首頁',
  MySubmissions: '我的提交',
  Settings: '我的設定',
  Management: '後台管理',
  Logout: '登出',
  Welcome_to: '歡迎來到',
  // announcements.vue
  Refresh: '重新整理',
  Back: '返回',
  No_Announcements: '暫無公告',
  // Setting.vue
  Profile: '個人資訊設定',
  Account: '帳號設定',
  Security: '安全設定',
  // AccoutSetting.vue
  ChangePassword: '更改密碼',
  ChangeEmail: '更改 E-mail',
  Update_Password: '更新密碼',
  // ProfileSetting.vue
  Avatar_Setting: '大頭貼設定',
  Profile_Setting: '個人資訊設定',
  // SecuritySettig
  Sessions: '登入記錄',
  Two_Factor_Authentication: '兩步驟驗證',
  // Login.vue
  LoginUsername: '使用者名稱',
  LoginPassword: '密碼',
  TFA_Code: '兩步驟驗證碼',
  No_Account: '沒有帳號,立即註冊!',
  Forget_Password: '忘記密碼',
  UserLogin: '登入',
  Welcome_back: '歡迎回來',
  // OIRank.vue
  OI_Ranklist: 'OI 排名',
  // OIContestRank.vue
  Total_Score: '總分',
  // ProblemList.vue
  Problem_List: '試題列表',
  High: '高級',
  Mid: '中級',
  Low: '初級',
  All: '全部',
  Reset: '重置',
  Pick_One: '選擇',
  Difficulty: '難度',
  Total: '總數',
  AC_Rate: '通過率',
  // Register.vue
  RegisterUsername: '使用者名稱',
  Email_Address: 'E-mail',
  RegisterPassword: '密碼',
  Password_Again: '確認密碼',
  Captcha: '驗證碼',
  UserRegister: '註冊',
  Already_Registed: '已經註冊? 現在登入!',
  The_username_already_exists: '此使用者名稱已經存在.',
  The_email_already_exists: '此電子郵件地址已被註冊',
  password_does_not_match: '無密碼相符',
  Thanks_for_registering: '感謝你的註冊,現在你可以登入了',
  // ResetPassword.vue and ApplyResetPassword.vue
  Reset_Password: '重設密碼',
  RPassword: '密碼',
  RPassword_Again: '確認密碼',
  RCaptcha: '驗證碼',
  ApplyEmail: 'E-mail',
  Send_Password_Reset_Email: '傳送密碼重設 E-mail',
  Your_password_has_been_reset: '你的密碼已重置',
  // Save.vue
  Save: '存檔',
  // Simditor.vue
  Uploading_is_in_progress: '上傳作業正在執行,你確定要離開當前頁面嗎??',
  // SubmissionDetails.vue
  Lang: '語言',
  Share: '分享',
  UnShare: '不分享',
  Succeeded: '成功',
  Real_Time: '真實時間',
  Signal: '訊號',
  // SubmissionList.vue
  When: '時間',
  ID: 'ID',
  Time: '時間',
  Memory: '記憶體空間',
  Author: '作者',
  Option: '選項',
  Mine: '我的',
  Search_Author: '搜尋作者',
  Accepted: '答案正確',
  Time_Limit_Exceeded: '超出時間限制',
  Memory_Limit_Exceeded: '超出記憶體空間限制',
  Runtime_Error: 'Runtime Error',
  System_Error: 'System Error',
  Pending: 'Pending',
  Partial_Accepted: 'Partial Accepted',
  Compile_Error: 'Compile Error',
  Rejudge: 'Rejudge',
  // UserHome.vue
  UserHomeSolved: '已解題數量',
  UserHomeserSubmissions: '提交次數',
  UserHomeScore: '分數',
  List_Solved_Problems: '已完成題目的列表',
  UserHomeIntro: '這個使用者尚未解題...'
}


================================================
FILE: src/pages/admin/App.vue
================================================
<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
  export default {
    name: 'app',
    components: {}
  }
</script>

<style lang="less">
  body {
    margin: 0;
    padding: 0;
    font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
    font-size: 14px;
    -webkit-font-smoothing: antialiased;
    background-color: #324157;
  }

  #app {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 100%;
  }
</style>


================================================
FILE: src/pages/admin/api.js
================================================
import Vue from 'vue'
import router from './router'
import axios from 'axios'
import utils from '@/utils/utils'

Vue.prototype.$http = axios
axios.defaults.baseURL = '/api'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.defaults.xsrfCookieName = 'csrftoken'

export default {
  // 登录
  login (username, password) {
    return ajax('login', 'post', {
      data: {
        username,
        password
      }
    })
  },
  logout () {
    return ajax('logout', 'get')
  },
  getProfile () {
    return ajax('profile', 'get')
  },
  // 获取公告列表
  getAnnouncementList (offset, limit) {
    return ajax('admin/announcement', 'get', {
      params: {
        paging: true,
        offset,
        limit
      }
    })
  },
  // 删除公告
  deleteAnnouncement (id) {
    return ajax('admin/announcement', 'delete', {
      params: {
        id
      }
    })
  },
  // 修改公告
  updateAnnouncement (data) {
    return ajax('admin/announcement', 'put', {
      data
    })
  },
  // 添加公告
  createAnnouncement (data) {
    return ajax('admin/announcement', 'post', {
      data
    })
  },
  // 获取用户列表
  getUserList (offset, limit, keyword) {
    let params = {paging: true, offset, limit}
    if (keyword) {
      params.keyword = keyword
    }
    return ajax('admin/user', 'get', {
      params: params
    })
  },
  // 获取单个用户信息
  getUser (id) {
    return ajax('admin/user', 'get', {
      params: {
        id
      }
    })
  },
  // 编辑用户
  editUser (data) {
    return ajax('admin/user', 'put', {
      data
    })
  },
  deleteUsers (id) {
    return ajax('admin/user', 'delete', {
      params: {
        id
      }
    })
  },
  importUsers (users) {
    return ajax('admin/user', 'post', {
      data: {
        users
      }
    })
  },
  generateUser (data) {
    return ajax('admin/generate_user', 'post', {
      data
    })
  },
  getLanguages () {
    return ajax('languages', 'get')
  },
  getSMTPConfig () {
    return ajax('admin/smtp', 'get')
  },
  createSMTPConfig (data) {
    return ajax('admin/smtp', 'post', {
      data
    })
  },
  editSMTPConfig (data) {
    return ajax('admin/smtp', 'put', {
      data
    })
  },
  testSMTPConfig (email) {
    return ajax('admin/smtp_test', 'post', {
      data: {
        email
      }
    })
  },
  getWebsiteConfig () {
    return ajax('admin/website', 'get')
  },
  editWebsiteConfig (data) {
    return ajax('admin/website', 'post', {
      data
    })
  },
  getJudgeServer () {
    return ajax('admin/judge_server', 'get')
  },
  deleteJudgeServer (hostname) {
    return ajax('admin/judge_server', 'delete', {
      params: {
        hostname: hostname
      }
    })
  },
  updateJudgeServer (data) {
    return ajax('admin/judge_server', 'put', {
      data
    })
  },
  getInvalidTestCaseList () {
    return ajax('admin/prune_test_case', 'get')
  },
  pruneTestCase (id) {
    return ajax('admin/prune_test_case', 'delete', {
      params: {
        id
      }
    })
  },
  createContest (data) {
    return ajax('admin/contest', 'post', {
      data
    })
  },
  getContest (id) {
    return ajax('admin/contest', 'get', {
      params: {
        id
      }
    })
  },
  editContest (data) {
    return ajax('admin/contest', 'put', {
      data
    })
  },
  getContestList (offset, limit, keyword) {
    let params = {paging: true, offset, limit}
    if (keyword) {
      params.keyword = keyword
    }
    return ajax('admin/contest', 'get', {
      params: params
    })
  },
  getContestAnnouncementList (contestID) {
    return ajax('admin/contest/announcement', 'get', {
      params: {
        contest_id: contestID
      }
    })
  },
  createContestAnnouncement (data) {
    return ajax('admin/contest/announcement', 'post', {
      data
    })
  },
  deleteContestAnnouncement (id) {
    return ajax('admin/contest/announcement', 'delete', {
      params: {
        id
      }
    })
  },
  updateContestAnnouncement (data) {
    return ajax('admin/contest/announcement', 'put', {
      data
    })
  },
  getProblemTagList (params) {
    return ajax('problem/tags', 'get', {
      params
    })
  },
  compileSPJ (data) {
    return ajax('admin/compile_spj', 'post', {
      data
    })
  },
  createProblem (data) {
    return ajax('admin/problem', 'post', {
      data
    })
  },
  editProblem (data) {
    return ajax('admin/problem', 'put', {
      data
    })
  },
  deleteProblem (id) {
    return ajax('admin/problem', 'delete', {
      params: {
        id
      }
    })
  },
  getProblem (id) {
    return ajax('admin/problem', 'get', {
      params: {
        id
      }
    })
  },
  getProblemList (params) {
    params = utils.filterEmptyValue(params)
    return ajax('admin/problem', 'get', {
      params
    })
  },
  getContestProblemList (params) {
    params = utils.filterEmptyValue(params)
    return ajax('admin/contest/problem', 'get', {
      params
    })
  },
  getContestProblem (id) {
    return ajax('admin/contest/problem', 'get', {
      params: {
        id
      }
    })
  },
  createContestProblem (data) {
    return ajax('admin/contest/problem', 'post', {
      data
    })
  },
  editContestProblem (data) {
    return ajax('admin/contest/problem', 'put', {
      data
    })
  },
  deleteContestProblem (id) {
    return ajax('admin/contest/problem', 'delete', {
      params: {
        id
      }
    })
  },
  makeContestProblemPublic (data) {
    return ajax('admin/contest_problem/make_public', 'post', {
      data
    })
  },
  addProblemFromPublic (data) {
    return ajax('admin/contest/add_problem_from_public', 'post', {
      data
    })
  },
  getReleaseNotes () {
    return ajax('admin/versions', 'get')
  },
  getDashboardInfo () {
    return ajax('admin/dashboard_info', 'get')
  },
  getSessions () {
    return ajax('sessions', 'get')
  },
  exportProblems (data) {
    return ajax('export_problem', 'post', {
      data
    })
  }
}

/**
 * @param url
 * @param method get|post|put|delete...
 * @param params like queryString. if a url is index?a=1&b=2, params = {a: '1', b: '2'}
 * @param data post data, use for method put|post
 * @returns {Promise}
 */
function ajax (url, method, options) {
  if (options !== undefined) {
    var {params = {}, data = {}} = options
  } else {
    params = data = {}
  }
  return new Promise((resolve, reject) => {
    axios({
      url,
      method,
      params,
      data
    }).then(res => {
      // API正常返回(status=20x), 是否错误通过有无error判断
      if (res.data.error !== null) {
        Vue.prototype.$error(res.data.data)
        reject(res)
        // // 若后端返回为登录,则为session失效,应退出当前登录用户
        if (res.data.data.startsWith('Please login')) {
          router.push({name: 'login'})
        }
      } else {
        resolve(res)
        if (method !== 'get') {
          Vue.prototype.$success('Succeeded')
        }
      }
    }, res => {
      // API请求异常,一般为Server error 或 network error
      reject(res)
      Vue.prototype.$error(res.data.data)
    })
  })
}


================================================
FILE: src/pages/admin/components/Accordion.vue
================================================
<template>
  <div class="accordion">
    <header>
      <h2>{{title}}</h2>
      <div class="header_right">
        <slot name="header"></slot>
      </div>
    </header>
    <div class="body" v-show="isOpen">
      <slot></slot>
    </div>
    <footer @click="isOpen = !isOpen"><i :class="{'rotate': !isOpen}" class="el-icon-caret-top"></i></footer>
  </div>
</template>

<script>
  export default{
    name: 'Accordion',
    props: {
      title: {
        type: String,
        required: true
      }
    },
    data () {
      return {
        isOpen: true
      }
    }
  }
</script>

<style lang="less" scoped>
.accordion{
  border: 1px solid #eaeefb;
  header{
    position: relative;
    h2{
      font-size: 14px;
      margin: 0 0 0 10px;
      line-height: 50px;
    }
    .header_right{
      right: 5px;
      top: 5px;
      position: absolute;
    }
  }
  .body{
    background-color: #f9fafc;
    border-top: 1px solid #eaeefb;
    clear: both;
    overflow: hidden;
    padding: 15px 10px;
  }
  footer{
    border-top: 1px solid #eaeefb;
    height: 36px;
    box-sizing: border-box;
    background-color: #fff;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    text-align: center;
    margin-top: -1px;
    color: #d3dce6;
    cursor: pointer;
    transition: .2s;
    &:hover{
      background-color: #f9fafc;
    }
    .rotate{
      transform: rotate(180deg);
    }
  }
}
</style>


================================================
FILE: src/pages/admin/components/CodeMirror.vue
================================================
<template>
  <codemirror v-model="currentValue" :options="options" ref="editor"></codemirror>
</template>
<script>
  import { codemirror } from 'vue-codemirror-lite'
  import 'codemirror/mode/clike/clike.js'
  import 'codemirror/mode/python/python.js'
  import 'codemirror/theme/solarized.css'

  export default {
    name: 'CodeMirror',
    data () {
      return {
        currentValue: '',
        options: {
          mode: 'text/x-csrc',
          lineNumbers: true,
          lineWrapping: false,
          theme: 'solarized',
          tabSize: 4,
          line: true,
          foldGutter: true,
          gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
          autofocus: true
        }
      }
    },
    components: {
      codemirror
    },
    props: {
      value: {
        type: String,
        default: ''
      },
      mode: {
        type: String,
        default: 'text/x-csrc'
      }
    },
    mounted () {
      this.currentValue = this.value
      this.$refs.editor.editor.setOption('mode', this.mode)
    },
    watch: {
      'value' (val) {
        if (this.currentValue !== val) {
          this.currentValue = val
        }
      },
      'currentValue' (newVal, oldVal) {
        if (newVal !== oldVal) {
          this.$emit('change', newVal)
          this.$emit('input', newVal)
        }
      },
      'mode' (newVal) {
        this.$refs.editor.editor.setOption('mode', newVal)
      }
    }
  }
</script>

<style>
  .CodeMirror {
    height: auto !important;
  }

  .CodeMirror-scroll {
    min-height: 300px;
    max-height: 1000px;
  }
</style>


================================================
FILE: src/pages/admin/components/KatexEditor.vue
================================================
<template>
  <el-form>
    <el-form-item :label="$t('m.Input')">
      <el-input type="textarea" v-model="input" @change="changeInput" @keyup.enter.native="changeInput"></el-input>
    </el-form-item>

    <el-form-item :label="$t('m.Output')">
    </el-form-item>
    <div v-html="text"></div>
  </el-form>
</template>

<script>
  import katex from 'katex'
  export default {
    name: '',
    data () {
      return {
        input: 'c = \\pm\\sqrt{a^2 + b^2}',
        text: ''
      }
    },
    mounted () {
      this.text = this.renderTex(this.input)
    },
    methods: {
      renderTex (data) {
        return katex.renderToString(data, {
          displayMode: true,
          throwOnError: false
        })
      },
      changeInput () {
        try {
          this.text = this.renderTex(this.input)
        } catch (e) {
          this.text = '<p style="text-align: center"><span style="color:red">Error Input</span></p>'
        }
      }
    }
  }
</script>

<style scoped>
</style>


================================================
FILE: src/pages/admin/components/Panel.vue
================================================
<template>
  <div class="panel" :class="{'small': small}">
    <header>
      <div class="title">
        <template v-if="$slots.title">
          <slot name="title"></slot>
        </template>
        <template v-else>
          {{title}}
        </template>
      </div>

      <div class="header_right">
        <slot name="header"></slot>
      </div>
    </header>

    <div class="body">
      <slot></slot>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'Panel',
    props: {
      title: {
        type: String,
        required: false
      },
      small: {
        type: Boolean,
        default: false
      }
    }
  }
</script>
<style scoped lang="less">
  .panel {
    margin-bottom: 20px;
    background-color: #fff;
    border: 1px solid transparent;
    border-radius: 4px;
    box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
    &.small {
      max-width: 830px;
      min-width: 700px;
      margin-left: 20px;
      margin-top: 10px;
    }
    header {
      position: relative;
      z-index: 10;
      > .title {
        margin: 0;
        color: #333;
        border-color: #ddd;
        font-size: 18px;
        font-weight: 300;
        letter-spacing: 0.025em;
        height: 60px;
        line-height: 45px;
        padding: 10px 15px;
        border-bottom: 1px solid #eee;
        border-top-left-radius: 3px;
        border-top-right-radius: 3px;
      }
      > .header_right {
        position: absolute;
        top: 50%;
        right: 20px;
        transform: translate(0, -50%);
      }
    }
    .body {
      padding: 15px;
    }
  }
</style>
<style lang="less">
  .panel-options {
    background-color: transparent;
    position: relative;
    height: 50px;
    button {
      margin-top: 18px;
      margin-right: 10px;
    }
    > .page {
      position: absolute;
      right:20px;
      top: 20px;
    }
  }
</style>


================================================
FILE: src/pages/admin/components/ScreenFull.vue
================================================
<template>
  <svg @click='click' class="icon screenfull" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
    t="1497503607356" viewBox="0 0 1024 1024" version="1.1" p-id="4109" :fill='color' :width="width" :height="height">
    <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"
      p-id="4110" />
  </svg>
</template>

<script>
import screenfull from 'screenfull'
export default {
  name: 'hamburger',
  props: {
    width: {
      type: Number,
      default: 22
    },
    height: {
      type: Number,
      default: 22
    },
    color: {
      type: String,
      default: '#5A5E66'
    }
  },
  data () {
    return {
      isFullscreen: false
    }
  },
  methods: {
    click () {
      if (!screenfull.enabled) {
        this.$warning('Your browser doesn\'t support fullscreen')
        return false
      }
      screenfull.toggle()
    }
  }
}
</script>

<style scoped>
.screenfull {
  display: inline-block;
  cursor: pointer;
  vertical-align: -0.15em;
}
</style>


================================================
FILE: src/pages/admin/components/SideMenu.vue
================================================
<template>
  <el-menu class="vertical_menu"
           :router="true" :default-active="currentPath">
    <div class="logo">
      <img src="../../../assets/logo.svg" alt="oj admin"/>
    </div>
    <el-menu-item index="/"><i class="el-icon-fa-dashboard"></i>{{$t('m.Dashboard')}}</el-menu-item>
    <el-submenu v-if="isSuperAdmin" index="general">
      <template slot="title"><i class="el-icon-menu"></i>{{$t('m.General')}}</template>
      <el-menu-item index="/user">{{$t('m.User')}}</el-menu-item>
      <el-menu-item index="/announcement">{{$t('m.Announcement')}}</el-menu-item>
      <el-menu-item index="/conf">{{$t('m.System_Config')}}</el-menu-item>
      <el-menu-item index="/judge-server">{{$t('m.Judge_Server')}}</el-menu-item>
      <el-menu-item index="/prune-test-case">{{$t('m.Prune_Test_Case')}}</el-menu-item>
    </el-submenu>
    <el-submenu index="problem" v-if="hasProblemPermission">
      <template slot="title"><i class="el-icon-fa-bars"></i>{{$t('m.Problem')}}</template>
      <el-menu-item index="/problems">{{$t('m.Problem_List')}}</el-menu-item>
      <el-menu-item index="/problem/create">{{$t('m.Create_Problem')}}</el-menu-item>
      <el-menu-item index="/problem/batch_ops">{{$t('m.Export_Import_Problem')}}</el-menu-item>

    </el-submenu>
    <el-submenu index="contest">
      <template slot="title"><i class="el-icon-fa-trophy"></i>{{$t('m.Contest')}}</template>
      <el-menu-item index="/contest">{{$t('m.Contest_List')}}</el-menu-item>
      <el-menu-item index="/contest/create">{{$t('m.Create_Contest')}}</el-menu-item>
    </el-submenu>
  </el-menu>
</template>

<script>
  import {mapGetters} from 'vuex'

  export default {
    name: 'SideMenu',
    data () {
      return {
        currentPath: ''
      }
    },
    mounted () {
      this.currentPath = this.$route.path
    },
    computed: {
      ...mapGetters(['user', 'isSuperAdmin', 'hasProblemPermission'])
    }
  }
</script>

<style scoped lang="less">
  .vertical_menu {
    overflow: auto;
    width: 205px;
    height: 100%;
    position: fixed !important;
    z-index: 100;
    top: 0;
    bottom: 0;
    left: 0;
    .logo {
      margin: 20px 0;
      text-align: center;
      img {
        background-color: #fff;
        border-radius: 50%;
        border: 3px solid #fff;
        width: 75px;
        height: 75px;
      }
    }
  }
</style>


================================================
FILE: src/pages/admin/components/Simditor.vue
================================================
<template>
  <textarea ref="editor"></textarea>
</template>

<script>
  import Simditor from 'tar-simditor'
  import 'tar-simditor/styles/simditor.css'
  import 'tar-simditor-markdown'
  import 'tar-simditor-markdown/styles/simditor-markdown.css'
  import './simditor-file-upload'

  export default {
    name: 'Simditor',
    props: {
      toolbar: {
        type: Array,
        default: () => ['title', 'bold', 'italic', 'underline', 'fontScale', 'color', 'ol', 'ul', '|', 'blockquote', 'code', 'link', 'table', 'image', 'uploadfile', 'hr', '|', 'indent', 'outdent', 'alignment', '|', 'markdown']
      },
      value: {
        type: String,
        default: ''
      }
    },
    data () {
      return {
        editor: null,
        currentValue: this.value
      }
    },
    mounted () {
      this.editor = new Simditor({
        textarea: this.$refs.editor,
        toolbar: this.toolbar,
        pasteImage: true,
        markdown: false,
        upload: {
          url: '/api/admin/upload_image/',
          params: null,
          fileKey: 'image',
          connectionCount: 3,
          leaveConfirm: this.$i18n.t('m.Uploading_is_in_progress')
        },
        allowedStyles: {
          span: ['color']
        }
      })
      this.editor.on('valuechanged', (e, src) => {
        this.currentValue = this.editor.getValue()
      })
      this.editor.on('decorate', (e, src) => {
        this.currentValue = this.editor.getValue()
      })

      this.editor.setValue(this.value)
    },
    watch: {
      'value' (val) {
        if (this.currentValue !== val) {
          this.currentValue = val
          this.editor.setValue(val)
        }
      },
      'currentValue' (newVal, oldVal) {
        if (newVal !== oldVal) {
          this.$emit('change', newVal)
          this.$emit('input', newVal)
        }
      }
    }
  }
</script>

<style lang="less" scoped>
</style>


================================================
FILE: src/pages/admin/components/TopNav.vue
================================================
<template>
  <div class="breadcrumb">
    <el-breadcrumb separator=">">
      <el-breadcrumb-item :to="{ path: '/' }">Home page</el-breadcrumb-item>
      <el-breadcrumb-item><slot name="topNavName">PLEASE OVERIDE ME</slot></el-breadcrumb-item>
    </el-breadcrumb>
  </div>
</template>

<style scoped>
  .breadcrumb {
    margin: 10px;
    margin-right: 25px;
    margin-bottom: 20px;
    padding: 15px;
    background-color: #fff;
  }
</style>


================================================
FILE: src/pages/admin/components/btn/Cancel.vue
================================================
<template>
  <el-button plain type="primary">{{$t('m.Cancel')}}</el-button>
</template>
<script>
  export default{
    name: 'Cancel'
  }
</script>


================================================
FILE: src/pages/admin/components/btn/IconBtn.vue
================================================
<template>
  <div style="display: inline-block;">
    <el-tooltip class="item" effect="dark" :content="name" placement="top">
      <el-button plain :icon="'el-icon-fa-' + icon" size="mini">
      </el-button>
    </el-tooltip>
  </div>
</template>

<script>
  export default {
    name: 'IconBtn',
    props: {
      name: {
        type: String,
        required: true
      },
      icon: {
        type: String,
        required: true
      }
    }
  }
</script>


================================================
FILE: src/pages/admin/components/btn/Save.vue
================================================
<template>
  <el-button type="primary">{{$t('m.Save')}}</el-button>
</template>
<script>
  export default{
    name: 'Save'
  }
</script>


================================================
FILE: src/pages/admin/components/infoCard.vue
================================================
<template>
  <el-card :body-style="{padding: 0, height: '100%'}" class="info-card">
    <el-row type="flex" class="info-card-container">
      <el-col :span="8" :style="{'background-color': color}" class="height-100">
        <i :class="['info-card-icon', icon]" :style="{'font-size': iconSize}"></i>
      </el-col>
      <el-col :span="16" class="info-card-text">
        <p :style="textStyle">{{value}}</p>
        <p style="font-weight: 300;">{{message}}</p>
      </el-col>
    </el-row>
  </el-card>
</template>

<script>
  export default {
    name: 'inforCard',
    props: {
      value: [String, Number],
      color: String,
      message: String,
      icon: String,
      iconSize: {
        type: String,
        default: '35px'
      },
      countSize: {
        type: String,
        default: '25px'
      },
      countWeight: {
        type: Number,
        default: 700
      }
    },
    computed: {
      textStyle () {
        return {
          'font-size': this.countSize,
          'font-weight': this.countWeight,
          color: this.color
        }
      }
    }
  }
</script>

<style lang="less" scoped>
  @card-height: 90px;
  .height-100 {
    height: 100%;
  }

  .info-card {
    display: inline-block;
    margin-right: 10px;
    width: 250px;
    height: @card-height;
    text-align: center;
    vertical-align: middle;

    &-container {
      height: 100%;
      align-items: center;
      justify-content: center;
    }
    &-icon {
      line-height: @card-height;
      color: white;
    }
    &-text {
      p {
        margin: 0;
        .minor-text {
          font-weight: 300;
          margin-top: 2px;
          font-size: 20px;
        }
      }
    }
  }
</style>


================================================
FILE: src/pages/admin/components/simditor-file-upload.js
================================================
/* eslint-disable */

import Simditor from 'tar-simditor'
import * as $ from 'jquery'

var UploadFile,
  __hasProp = {}.hasOwnProperty,
  __extends = function (child, parent) {
    for (var key in parent) {
      if (__hasProp.call(parent, key)) child[key] = parent[key];
    }

    function ctor() {
      this.constructor = child;
    }

    ctor.prototype = parent.prototype;
    child.prototype = new ctor();
    child.__super__ = parent.prototype;
    return child;
  },
  __slice = [].slice;

UploadFile = (function (_super) {
  __extends(UploadFile, _super);

  UploadFile.i18n = {
    'zh-CN': {
      uploadfile: '上传文件'
    },
    'en-US': {
      uploadfile: 'upload file'
    }
  };

  UploadFile.prototype.name = 'uploadfile';

  UploadFile.prototype.icon = 'upload';

  function UploadFile() {
    var args;
    args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
    UploadFile.__super__.constructor.apply(this, args);
    this._initUpload();
  }

  UploadFile.prototype._initUpload = function () {
    this.input = $('<input />', {
      type: 'file',
      style: 'position:absolute;top:0;right:0;height:100%;width:100%;opacity:0;filter:alpha(opacity=0);cursor:pointer;'
    }).prependTo(this.el)
    var _this = this;
    this.el.on('click mousedown', 'input[type=file]', function (e) {
      return e.stopPropagation();
    }).on('change', 'input[type=file]', function (e) {

      var formData = new FormData();
      formData.append('file', this.files[0]);
      $.ajax({
        url: '/api/admin/upload_file',
        type: 'POST',
        cache: false,
        data: formData,
        processData: false,
        contentType: false
      }).done(function (res) {
        if (!res.success) {
          alert("upload file failed")
        } else {
          let link = '<a target="_blank" className="simditor-attach-link" href="' + res.file_path + '">' + res.file_name + '</a>'
          _this.editor.setValue(_this.editor.getValue() + link)
        }
      }).fail(function (res) {
        alert("upload file failed")
      });
    });
  }

  return UploadFile;

})(Simditor.Button);

Simditor.Toolbar.addButton(UploadFile);




================================================
FILE: src/pages/admin/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <title>OnlineJudge</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
  <meta name="renderer" content="webkit"/>
  <link rel="shortcut icon" href="/public/website/favicon.ico">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>


================================================
FILE: src/pages/admin/index.js
================================================
import 'babel-polyfill'
import Vue from 'vue'
import App from './App.vue'
import store from '@/store'
import i18n from '@/i18n'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

import filters from '@/utils/filters'
import router from './router'
import { GOOGLE_ANALYTICS_ID } from '@/utils/constants'
import VueAnalytics from 'vue-analytics'
import katex from '@/plugins/katex'

import Panel from './components/Panel.vue'
import IconBtn from './components/btn/IconBtn.vue'
import Save from './components/btn/Save.vue'
import Cancel from './components/btn/Cancel.vue'
import './style.less'

// register global utility filters.
Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

Vue.use(VueAnalytics, {
  id: GOOGLE_ANALYTICS_ID,
  router
})
Vue.use(katex)
Vue.component(IconBtn.name, IconBtn)
Vue.component(Panel.name, Panel)
Vue.component(Save.name, Save)
Vue.component(Cancel.name, Cancel)

Vue.use(Element, {
  i18n: (key, value) => i18n.t(key, value)
})

Vue.prototype.$error = (msg) => {
  Vue.prototype.$message({'message': msg, 'type': 'error'})
}

Vue.prototype.$warning = (msg) => {
  Vue.prototype.$message({'message': msg, 'type': 'warning'})
}

Vue.prototype.$success = (msg) => {
  if (!msg) {
    Vue.prototype.$message({'message': 'Succeeded', 'type': 'success'})
  } else {
    Vue.prototype.$message({'message': msg, 'type': 'success'})
  }
}

new Vue(Vue.util.extend({router, store, i18n}, App)).$mount('#app')


================================================
FILE: src/pages/admin/router.js
================================================
import Vue from 'vue'
import VueRouter from 'vue-router'
// 引入 view 组件
import { Announcement, Conf, Contest, ContestList, Home, JudgeServer, Login,
  Problem, ProblemList, User, PruneTestCase, Dashboard, ProblemImportOrExport } from './views'
Vue.use(VueRouter)

export default new VueRouter({
  mode: 'history',
  base: '/admin/',
  scrollBehavior: () => ({y: 0}),
  routes: [
    {
      path: '/login',
      name: 'login',
      component: Login
    },
    {
      path: '/',
      component: Home,
      children: [
        {
          path: '',
          name: 'dashboard',
          component: Dashboard
        },
        {
          path: '/announcement',
          name: 'announcement',
          component: Announcement
        },
        {
          path: '/user',
          name: 'user',
          component: User
        },
        {
          path: '/conf',
          name: 'conf',
          component: Conf
        },
        {
          path: '/judge-server',
          name: 'judge-server',
          component: JudgeServer
        },
        {
          path: '/prune-test-case',
          name: 'prune-test-case',
          component: PruneTestCase
        },
        {
          path: '/problems',
          name: 'problem-list',
          component: ProblemList
        },
        {
          path: '/problem/create',
          name: 'create-problem',
          component: Problem
        },
        {
          path: '/problem/edit/:problemId',
          name: 'edit-problem',
          component: Problem
        },
        {
          path: '/problem/batch_ops',
          name: 'problem_batch_ops',
          component: ProblemImportOrExport
        },
        {
          path: '/contest/create',
          name: 'create-contest',
          component: Contest
        },
        {
          path: '/contest',
          name: 'contest-list',
          component: ContestList
        },
        {
          path: '/contest/:contestId/edit',
          name: 'edit-contest',
          component: Contest
        },
        {
          path: '/contest/:contestId/announcement',
          name: 'contest-announcement',
          component: Announcement
        },
        {
          path: '/contest/:contestId/problems',
          name: 'contest-problem-list',
          component: ProblemList
        },
        {
          path: '/contest/:contestId/problem/create',
          name: 'create-contest-problem',
          component: Problem
        },
        {
          path: '/contest/:contestId/problem/:problemId/edit',
          name: 'edit-contest-problem',
          component: Problem
        }
      ]
    },
    {
      path: '*', redirect: '/login'
    }
  ]
})


================================================
FILE: src/pages/admin/style.less
================================================
[class^="el-icon-fa"], [class*=" el-icon-fa"] {
  font-family: FontAwesome !important;
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  display: inline-block;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

@import url("../../../node_modules/font-awesome/less/font-awesome");
@fa-css-prefix: el-icon-fa;


================================================
FILE: src/pages/admin/views/Home.vue
================================================
<template>
  <div class="container">
    <div>
      <SideMenu></SideMenu>
    </div>
    <div id="header">
      <i class="el-icon-fa-font katex-editor" @click="katexVisible=true" ></i>
      <screen-full :width="14" :height="14" class="screen-full"></screen-full>
      <el-dropdown @command="handleCommand">
        <span>{{user.username}}<i class="el-icon-caret-bottom el-icon--right"></i></span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item command="logout">Logout</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
    </div>
    <div class="content-app">
      <transition name="fadeInUp" mode="out-in">
        <router-view></router-view>
      </transition>
      <div class="footer">
        Build Version: {{ version }}
      </div>
    </div>

    <el-dialog :title="$t('m.Latex_Editor')" :visible.sync="katexVisible">
      <KatexEditor></KatexEditor>
    </el-dialog>
  </div>
</template>

<script>
  import { types } from '@/store'
  import { mapGetters } from 'vuex'
  import SideMenu from '../components/SideMenu.vue'
  import ScreenFull from '@admin/components/ScreenFull.vue'
  import KatexEditor from '@admin/components/KatexEditor.vue'
  import api from '../api'

  export default {
    name: 'app',
    data () {
      return {
        version: process.env.VERSION,
        katexVisible: false
      }
    },
    components: {
      SideMenu,
      KatexEditor,
      ScreenFull
    },
    beforeRouteEnter (to, from, next) {
      api.getProfile().then(res => {
        if (!res.data.data) {
          // not login
          next({name: 'login'})
        } else {
          next(vm => {
            vm.$store.commit(types.CHANGE_PROFILE, {profile: res.data.data})
          })
        }
      })
    },
    methods: {
      handleCommand (command) {
        if (command === 'logout') {
          api.logout().then(() => {
            this.$router.push({name: 'login'})
          })
        }
      }
    },
    computed: {
      ...mapGetters(['user'])
    }
  }
</script>

<style lang="less">
  a {
    background-color: transparent;
  }

  a:active, a:hover {
    outline-width: 0
  }

  img {
    border-style: none
  }

  .container {
    overflow: auto;
    font-weight: 400;
    height: 100%;
    -webkit-font-smoothing: antialiased;
    background-color: #EDECEC;
    overflow-y: scroll;
    min-width: 1000px;
  }

  * {
    box-sizing: border-box;
  }

  #header {
    text-align: right;
    padding-left: 210px;
    padding-right: 30px;
    line-height: 50px;
    height: 50px;
    background: #F9FAFC;
    .screen-full {
      margin-right: 8px;
    }
  }

  .content-app {
    padding-top: 20px;
    padding-right: 10px;
    padding-left: 210px;
  }

  .footer {
    margin: 15px;
    text-align: center;
    font-size: small;
  }

  @keyframes fadeInUp {
    from {
      opacity: 0;
      transform: translate(0, 30px);
    }

    to {
      opacity: 1;
      transform: none;
    }
  }

  .fadeInUp-enter-active {
    animation: fadeInUp .8s;
  }

  .katex-editor {
    margin-right: 5px;
    /*font-size: 18px;*/
  }



</style>


================================================
FILE: src/pages/admin/views/contest/Contest.vue
================================================
<template>
  <div class="view">
    <Panel :title="title">
      <el-form label-position="top">
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item :label="$t('m.ContestTitle')" required>
              <el-input v-model="contest.title" :placeholder="$t('m.ContestTitle')"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item :label="$t('m.ContestDescription')" required>
              <Simditor v-model="contest.description"></Simditor>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Contest_Start_Time')" required>
              <el-date-picker
                v-model="contest.start_time"
                type="datetime"
                :placeholder="$t('m.Contest_Start_Time')">
              </el-date-picker>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Contest_End_Time')" required>
              <el-date-picker
                v-model="contest.end_time"
                type="datetime"
                :placeholder="$t('m.Contest_End_Time')">
              </el-date-picker>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Contest_Password')">
              <el-input v-model="contest.password" :placeholder="$t('m.Contest_Password')"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Contest_Rule_Type')">
              <el-radio class="radio" v-model="contest.rule_type" label="ACM" :disabled="disableRuleType">ACM</el-radio>
              <el-radio class="radio" v-model="contest.rule_type" label="OI" :disabled="disableRuleType">OI</el-radio>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Real_Time_Rank')">
              <el-switch
                v-model="contest.real_time_rank"
                active-color="#13ce66"
                inactive-color="#ff4949">
              </el-switch>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Contest_Status')">
              <el-switch
                v-model="contest.visible"
                active-text=""
                inactive-text="">
              </el-switch>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item :label="$t('m.Allowed_IP_Ranges')">
              <div v-for="(range, index) in contest.allowed_ip_ranges" :key="index">
                <el-row :gutter="20" style="margin-bottom: 15px">
                  <el-col :span="8">
                    <el-input v-model="range.value" :placeholder="$t('m.CIDR_Network')"></el-input>
                  </el-col>
                  <el-col :span="10">
                    <el-button plain icon="el-icon-fa-plus" @click="addIPRange"></el-button>
                    <el-button plain icon="el-icon-fa-trash" @click="removeIPRange(range)"></el-button>
                  </el-col>
                </el-row>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <save @click.native="saveContest"></save>
    </Panel>
  </div>
</template>

<script>
  import api from '../../api.js'
  import Simditor from '../../components/Simditor.vue'

  export default {
    name: 'CreateContest',
    components: {
      Simditor
    },
    data () {
      return {
        title: 'Create Contest',
        disableRuleType: false,
        contest: {
          title: '',
          description: '',
          start_time: '',
          end_time: '',
          rule_type: 'ACM',
          password: '',
          real_time_rank: true,
          visible: true,
          allowed_ip_ranges: [{
            value: ''
          }]
        }
      }
    },
    methods: {
      saveContest () {
        let funcName = this.$route.name === 'edit-contest' ? 'editContest' : 'createContest'
        let data = Object.assign({}, this.contest)
        let ranges = []
        for (let v of data.allowed_ip_ranges) {
          if (v.value !== '') {
            ranges.push(v.value)
          }
        }
        data.allowed_ip_ranges = ranges
        api[funcName](data).then(res => {
          this.$router.push({name: 'contest-list', query: {refresh: 'true'}})
        }).catch(() => {
        })
      },
      addIPRange () {
        this.contest.allowed_ip_ranges.push({value: ''})
      },
      removeIPRange (range) {
        let index = this.contest.allowed_ip_ranges.indexOf(range)
        if (index !== -1) {
          this.contest.allowed_ip_ranges.splice(index, 1)
        }
      }
    },
    mounted () {
      if (this.$route.name === 'edit-contest') {
        this.title = 'Edit Contest'
        this.disableRuleType = true
        api.getContest(this.$route.params.contestId).then(res => {
          let data = res.data.data
          let ranges = []
          for (let v of data.allowed_ip_ranges) {
            ranges.push({value: v})
          }
          if (ranges.length === 0) {
            ranges.push({value: ''})
          }
          data.allowed_ip_ranges = ranges
          this.contest = data
        }).catch(() => {
        })
      }
    }
  }
</script>


================================================
FILE: src/pages/admin/views/contest/ContestList.vue
================================================
<template>
  <div class="view">
    <Panel title="Contest List">
      <div slot="header">
        <el-input
          v-model="keyword"
          prefix-icon="el-icon-search"
          placeholder="Keywords">
        </el-input>
      </div>
      <el-table
        v-loading="loading"
        element-loading-text="loading"
        ref="table"
        :data="contestList"
        style="width: 100%">
        <el-table-column type="expand">
          <template slot-scope="props">
            <p>Start Time: {{props.row.start_time | localtime }}</p>
            <p>End Time: {{props.row.end_time | localtime }}</p>
            <p>Create Time: {{props.row.create_time | localtime}}</p>
            <p>Creator: {{props.row.created_by.username}}</p>
          </template>
        </el-table-column>
        <el-table-column
          prop="id"
          width="80"
          label="ID">
        </el-table-column>
        <el-table-column
          prop="title"
          label="Title">
        </el-table-column>
        <el-table-column
          label="Rule Type"
          width="130">
          <template slot-scope="scope">
            <el-tag type="gray">{{scope.row.rule_type}}</el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="Contest Type"
          width="180">
          <template slot-scope="scope">
            <el-tag :type="scope.row.contest_type === 'Public' ? 'success' : 'primary'">
              {{ scope.row.contest_type}}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          label="Status"
          width="130">
          <template slot-scope="scope">
            <el-tag
              :type="scope.row.status === '-1' ? 'danger' : scope.row.status === '0' ? 'success' : 'primary'">
              {{ scope.row.status | contestStatus}}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          width="100"
          label="Visible">
          <template slot-scope="scope">
            <el-switch v-model="scope.row.visible"
                       active-text=""
                       inactive-text=""
                       @change="handleVisibleSwitch(scope.row)">
            </el-switch>
          </template>
        </el-table-column>
        <el-table-column
          fixed="right"
          width="250"
          label="Operation">
          <div slot-scope="scope">
            <icon-btn name="Edit" icon="edit" @click.native="goEdit(scope.row.id)"></icon-btn>
            <icon-btn name="Problem" icon="list-ol" @click.native="goContestProblemList(scope.row.id)"></icon-btn>
            <icon-btn name="Announcement" icon="info-circle"
                      @click.native="goContestAnnouncement(scope.row.id)"></icon-btn>
            <icon-btn icon="download" name="Download Accepted Submissions"
                      @click.native="openDownloadOptions(scope.row.id)"></icon-btn>
          </div>
        </el-table-column>
      </el-table>
      <div class="panel-options">
        <el-pagination
          class="page"
          layout="prev, pager, next"
          @current-change="currentChange"
          :page-size="pageSize"
          :total="total">
        </el-pagination>
      </div>
    </Panel>
    <el-dialog title="Download Contest Submissions"
               width="30%"
               :visible.sync="downloadDialogVisible">
      <el-switch v-model="excludeAdmin" active-text="Exclude admin submissions"></el-switch>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="downloadSubmissions">确 定</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
  import api from '../../api.js'
  import utils from '@/utils/utils'
  import {CONTEST_STATUS_REVERSE} from '@/utils/constants'

  export default {
    name: 'ContestList',
    data () {
      return {
        pageSize: 10,
        total: 0,
        contestList: [],
        keyword: '',
        loading: false,
        excludeAdmin: true,
        currentPage: 1,
        currentId: 1,
        downloadDialogVisible: false
      }
    },
    mounted () {
      this.getContestList(this.currentPage)
    },
    filters: {
      contestStatus (value) {
        return CONTEST_STATUS_REVERSE[value].name
      }
    },
    methods: {
      // 切换页码回调
      currentChange (page) {
        this.currentPage = page
        this.getContestList(page)
      },
      getContestList (page) {
        this.loading = true
        api.getContestList((page - 1) * this.pageSize, this.pageSize, this.keyword).then(res => {
          this.loading = false
          this.total = res.data.data.total
          this.contestList = res.data.data.results
        }, res => {
          this.loading = false
        })
      },
      openDownloadOptions (contestId) {
        this.downloadDialogVisible = true
        this.currentId = contestId
      },
      downloadSubmissions () {
        let excludeAdmin = this.excludeAdmin ? '1' : '0'
        let url = `/admin/download_submissions?contest_id=${this.currentId}&exclude_admin=${excludeAdmin}`
        utils.downloadFile(url)
      },
      goEdit (contestId) {
        this.$router.push({name: 'edit-contest', params: {contestId}})
      },
      goContestAnnouncement (contestId) {
        this.$router.push({name: 'contest-announcement', params: {contestId}})
      },
      goContestProblemList (contestId) {
        this.$router.push({name: 'contest-problem-list', params: {contestId}})
      },
      handleVisibleSwitch (row) {
        api.editContest(row)
      }
    },
    watch: {
      'keyword' () {
        this.currentChange(1)
      }
    }
  }
</script>


================================================
FILE: src/pages/admin/views/general/Announcement.vue
================================================
<template>
  <div class="announcement view">
    <Panel :title="$t('m.General_Announcement')">
      <div class="list">
        <el-table
          v-loading="loading"
          element-loading-text="loading"
          ref="table"
          :data="announcementList"
          style="width: 100%">
          <el-table-column
            width="100"
            prop="id"
            label="ID">
          </el-table-column>
          <el-table-column
            prop="title"
            label="Title">
          </el-table-column>
          <el-table-column
            prop="create_time"
            label="CreateTime">
            <template slot-scope="scope">
              {{ scope.row.create_time | localtime }}
            </template>
          </el-table-column>
          <el-table-column
            prop="last_update_time"
            label="LastUpdateTime">
            <template slot-scope="scope">
              {{scope.row.last_update_time | localtime }}
            </template>
          </el-table-column>
          <el-table-column
            prop="created_by.username"
            label="Author">
          </el-table-column>
          <el-table-column
            width="100"
            prop="visible"
            label="Visible">
            <template slot-scope="scope">
              <el-switch v-model="scope.row.visible"
                         active-text=""
                         inactive-text=""
                         @change="handleVisibleSwitch(scope.row)">
              </el-switch>
            </template>
          </el-table-column>
          <el-table-column
            fixed="right"
            label="Option"
            width="200">
            <div slot-scope="scope">
              <icon-btn name="Edit" icon="edit" @click.native="openAnnouncementDialog(scope.row.id)"></icon-btn>
              <icon-btn name="Delete" icon="trash" @click.native="deleteAnnouncement(scope.row.id)"></icon-btn>
            </div>
          </el-table-column>
        </el-table>
        <div class="panel-options">
          <el-button type="primary" size="small" @click="openAnnouncementDialog(null)" icon="el-icon-plus">Create</el-button>
          <el-pagination
            v-if="!contestID"
            class="page"
            layout="prev, pager, next"
            @current-change="currentChange"
            :page-size="pageSize"
            :total="total">
          </el-pagination>
        </div>
      </div>
    </Panel>
    <!--对话框-->
    <el-dialog :title="announcementDialogTitle" :visible.sync="showEditAnnouncementDialog"
               @open="onOpenEditDialog" :close-on-click-modal="false">
      <el-form label-position="top">
        <el-form-item :label="$t('m.Announcement_Title')" required>
          <el-input
            v-model="announcement.title"
            :placeholder="$t('m.Announcement_Title')" class="title-input">
          </el-input>
        </el-form-item>
        <el-form-item :label="$t('m.Announcement_Content')" required>
          <Simditor v-model="announcement.content"></Simditor>
        </el-form-item>
        <div class="visible-box">
          <span>{{$t('m.Announcement_visible')}}</span>
          <el-switch
            v-model="announcement.visible"
            active-text=""
            inactive-text="">
          </el-switch>
        </div>
      </el-form>
      <span slot="footer" class="dialog-footer">
          <cancel @click.native="showEditAnnouncementDialog = false"></cancel>
          <save type="primary" @click.native="submitAnnouncement"></save>
        </span>
    </el-dialog>
  </div>
</template>

<script>
  import Simditor from '../../components/Simditor.vue'
  import api from '../../api.js'

  export default {
    name: 'Announcement',
    components: {
      Simditor
    },
    data () {
      return {
        contestID: '',
        // 显示编辑公告对话框
        showEditAnnouncementDialog: false,
        // 公告列表
        announcementList: [],
        // 一页显示的公告数
        pageSize: 15,
        // 总公告数
        total: 0,
        // 当前公告id
        currentAnnouncementId: null,
        mode: 'create',
        // 公告 (new | edit) model
        announcement: {
          title: '',
          visible: true,
          content: ''
        },
        // 对话框标题
        announcementDialogTitle: 'Edit Announcement',
        // 是否显示loading
        loading: true,
        // 当前页码
        currentPage: 0
      }
    },
    mounted () {
      this.init()
    },
    methods: {
      init () {
        this.contestID = this.$route.params.contestId
        if (this.contestID) {
          this.getContestAnnouncementList()
        } else {
          this.getAnnouncementList(1)
        }
      },
      // 切换页码回调
      currentChange (page) {
        this.currentPage = page
        this.getAnnouncementList(page)
      },
      getAnnouncementList (page) {
        this.loading = true
        api.getAnnouncementList((page - 1) * this.pageSize, this.pageSize).then(res => {
          this.loading = false
          this.total = res.data.data.total
          this.announcementList = res.data.data.results
        }, res => {
          this.loading = false
        })
      },
      getContestAnnouncementList () {
        this.loading = true
        api.getContestAnnouncementList(this.contestID).then(res => {
          this.loading = false
          this.announcementList = res.data.data
        }).catch(() => {
          this.loading = false
        })
      },
      // 打开编辑对话框的回调
      onOpenEditDialog () {
        // todo 优化
        // 暂时解决 文本编辑器显示异常bug
        setTimeout(() => {
          if (document.createEvent) {
            let event = document.createEvent('HTMLEvents')
            event.initEvent('resize', true, true)
            window.dispatchEvent(event)
          } else if (document.createEventObject) {
            window.fireEvent('onresize')
          }
        }, 0)
      },
      // 提交编辑
      // 默认传入MouseEvent
      submitAnnouncement (data = undefined) {
        let funcName = ''
        if (!data.title) {
          data = {
            id: this.currentAnnouncementId,
            title: this.announcement.title,
            content: this.announcement.content,
            visible: this.announcement.visible
          }
        }
        if (this.contestID) {
          data.contest_id = this.contestID
          funcName = this.mode === 'edit' ? 'updateContestAnnouncement' : 'createContestAnnouncement'
        } else {
          funcName = this.mode === 'edit' ? 'updateAnnouncement' : 'createAnnouncement'
        }
        api[funcName](data).then(res => {
          this.showEditAnnouncementDialog = false
          this.init()
        }).catch()
      },
      // 删除公告
      deleteAnnouncement (announcementId) {
        this.$confirm('Are you sure you want to delete this announcement?', 'Warning', {
          confirmButtonText: 'Delete',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          // then 为确定
          this.loading = true
          let funcName = this.contestID ? 'deleteContestAnnouncement' : 'deleteAnnouncement'
          api[funcName](announcementId).then(res => {
            this.loading = true
            this.init()
          })
        }).catch(() => {
          // catch 为取消
          this.loading = false
        })
      },
      openAnnouncementDialog (id) {
        this.showEditAnnouncementDialog = true
        if (id !== null) {
          this.currentAnnouncementId = id
          this.announcementDialogTitle = 'Edit Announcement'
          this.announcementList.find(item => {
            if (item.id === this.currentAnnouncementId) {
              this.announcement.title = item.title
              this.announcement.visible = item.visible
              this.announcement.content = item.content
              this.mode = 'edit'
            }
          })
        } else {
          this.announcementDialogTitle = 'Create Announcement'
          this.announcement.title = ''
          this.announcement.visible = true
          this.announcement.content = ''
          this.mode = 'create'
        }
      },
      handleVisibleSwitch (row) {
        this.mode = 'edit'
        this.submitAnnouncement({
          id: row.id,
          title: row.title,
          content: row.content,
          visible: row.visible
        })
      }
    },
    watch: {
      $route () {
        this.init()
      }
    }
  }
</script>

<style lang="less" scoped>
  .title-input {
    margin-bottom: 20px;
  }

  .visible-box {
    margin-top: 10px;
    width: 205px;
    float: left;
  }
</style>


================================================
FILE: src/pages/admin/views/general/Conf.vue
================================================
<template>
  <div class="view">
    <Panel :title="$t('m.SMTP_Config')">
      <el-form label-position="left" label-width="70px" :model="smtp">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item :label="$t('m.Server')" required>
              <el-input v-model="smtp.server" placeholder="SMTP Server Address"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.Port')" required>
              <el-input type="number" v-model="smtp.port" placeholder="SMTP Server Port"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.Email')" required>
              <el-input v-model="smtp.email" placeholder="Account Used To Send Email"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.Password')" label-width="90px" required>
              <el-input v-model="smtp.password" type="password" placeholder="SMTP Server Password"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="TLS">
              <el-switch
                v-model="smtp.tls">
              </el-switch>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <el-button type="primary" @click="saveSMTPConfig">Save</el-button>
      <el-button type="warning" @click="testSMTPConfig"
                 v-if="saved" :loading="loadingBtnTest">Send Test Email</el-button>
    </Panel>

    <Panel :title="$t('m.Website_Config')">
      <el-form label-position="left" label-width="100px" ref="form" :model="websiteConfig">
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item :label="$t('m.Base_Url')" required>
              <el-input v-model="websiteConfig.website_base_url" placeholder="Website Base Url"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Name')" required>
              <el-input v-model="websiteConfig.website_name" placeholder="Website Name"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Shortcut')" required>
              <el-input v-model="websiteConfig.website_name_shortcut" placeholder="Website Name Shortcut"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item :label="$t('m.Footer')" required>
              <el-input type="textarea" :autosize="{ minRows: 2, maxRows: 4}" v-model="websiteConfig.website_footer"
                        placeholder="Website Footer HTML"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-col :span="12">
              <el-form-item :label="$t('m.Allow_Register')" label-width="200px">
                <el-switch
                  v-model="websiteConfig.allow_register"
                  active-color="#13ce66"
                  inactive-color="#ff4949">
                </el-switch>
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item :label="$t('m.Submission_List_Show_All')" label-width="200px">
                <el-switch
                  v-model="websiteConfig.submission_list_show_all"
                  active-color="#13ce66"
                  inactive-color="#ff4949">
                </el-switch>
              </el-form-item>
            </el-col>
          </el-col>
        </el-row>
      </el-form>
      <save @click.native="saveWebsiteConfig"></save>
    </Panel>
  </div>
</template>

<script>
  import api from '../../api.js'

  export default {
    name: 'Conf',
    data () {
      return {
        init: false,
        saved: false,
        loadingBtnTest: false,
        smtp: {
          server: 'smtp.example.com',
          port: 25,
          password: '',
          email: 'email@example.com',
          tls: true
        },
        websiteConfig: {}
      }
    },
    mounted () {
      api.getSMTPConfig().then(res => {
        if (res.data.data) {
          this.smtp = res.data.data
        } else {
          this.init = true
          this.$warning('Please setup SMTP config at first')
        }
      })
      api.getWebsiteConfig().then(res => {
        this.websiteConfig = res.data.data
      }).catch(() => {
      })
    },
    methods: {
      saveSMTPConfig () {
        if (!this.init) {
          api.editSMTPConfig(this.smtp).then(() => {
            this.saved = true
          }, () => {
          })
        } else {
          api.createSMTPConfig(this.smtp).then(() => {
            this.saved = true
          }, () => {
          })
        }
      },
      testSMTPConfig () {
        this.$prompt('Please input your email', '', {
          inputPattern: /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
          inputErrorMessage: 'Error email format'
        }).then(({value}) => {
          this.loadingBtnTest = true
          api.testSMTPConfig(value).then(() => {
            this.loadingBtnTest = false
          }, () => {
            this.loadingBtnTest = false
          })
        }).catch(() => {
        })
      },
      saveWebsiteConfig () {
        api.editWebsiteConfig(this.websiteConfig).then(() => {
        }).catch(() => {
        })
      }
    }
  }
</script>


================================================
FILE: src/pages/admin/views/general/Dashboard.vue
================================================
<template>
  <el-row type="flex" :gutter="20">
    <el-col :md="10" :lg="8">
      <el-card class="admin-info">
        <el-row :gutter="20">
          <el-col :span="10">
            <img class="avatar" :src="profile.avatar"/>
          </el-col>
          <el-col :span="14">
            <p class="admin-info-name">{{user.username}}</p>
            <p>{{user.admin_type}}</p>
          </el-col>
        </el-row>
        <hr/>
        <div class="last-info">
          <p class="last-info-title">{{$t('m.Last_Login')}}</p>
          <el-form label-width="80px" class="last-info-body">
            <el-form-item label="Time:">
              <span>{{session.last_activity | localtime}}</span>
            </el-form-item>
            <el-form-item label="IP:">
              <span>{{session.ip}}</span>
            </el-form-item>
            <el-form-item label="OS">
              <span>{{os}}</span>
            </el-form-item>
            <el-form-item label="Browser:">
              <span>{{browser}}</span>
            </el-form-item>
          </el-form>
        </div>
      </el-card>
      <panel :title="$t('m.System_Overview')" v-if="isSuperAdmin">
        <p>{{$t('m.DashBoardJudge_Server')}}:  {{infoData.judge_server_count}}</p>
        <p>{{$t('m.HTTPS_Status')}}:
          <el-tag :type="https ? 'success' : 'danger'" size="small">
            {{ https ? 'Enabled' : 'Disabled'}}
          </el-tag>
        </p>
        <p>{{$t('m.Force_HTTPS')}}:
          <el-tag :type="forceHttps ? 'success' : 'danger'" size="small">
            {{forceHttps ? 'Enabled' : 'Disabled'}}
          </el-tag>
        </p>
        <p>{{$t('m.CDN_HOST')}}:
          <el-tag :type="cdn ? 'success' : 'warning'" size="small">
            {{cdn ? cdn : 'Not Use'}}
          </el-tag>
        </p>
      </panel>
    </el-col>

    <el-col :md="14" :lg="16" v-if="isSuperAdmin">
      <div class="info-container">
        <info-card color="#909399" icon="el-icon-fa-users" message="Total Users" iconSize="30px" class="info-item"
                   :value="infoData.user_count"></info-card>
        <info-card color="#67C23A" icon="el-icon-fa-list" message="Today Submissions" class="info-item"
                   :value="infoData.today_submission_count"></info-card>
        <info-card color="#409EFF" icon="el-icon-fa-trophy" message="Recent Contests" class="info-item"
                   :value="infoData.recent_contest_count"></info-card>
      </div>
      <panel style="margin-top: 5px">
        <span slot="title" v-loading="loadingReleases">Release Notes
        <el-popover placement="right" trigger="hover">
          <i slot="reference" class="el-icon-fa-question-circle import-user-icon"></i>
          <p>Please upgrade to the latest version to enjoy the new features. </p>
          <p>Reference: <a href="http://docs.onlinejudge.me/#/onlinejudge/guide/upgrade" target="_blank">
          http://docs.onlinejudge.me/#/onlinejudge/guide/upgrade</a>
          </p>
        </el-popover>
        </span>

        <el-collapse v-model="activeNames" v-for="(release, index) of releases" :key="'release' + index">
          <el-collapse-item :name="index+1">
            <template slot="title">
              <div v-if="release.new_version">{{release.title}}
                <el-tag size="mini" type="success">New Version</el-tag>
              </div>
              <span v-else>{{release.title}}</span>
            </template>
            <p>Level: {{release.level}}</p>
            <p>Details: </p>
            <div class="release-body">
              <ul v-for="detail in release.details" :key="detail">
                <li v-html="detail"></li>
              </ul>
            </div>
          </el-collapse-item>
        </el-collapse>
      </panel>
    </el-col>
  </el-row>
</template>


<script>
  import { mapGetters } from 'vuex'
  import browserDetector from 'browser-detect'
  import InfoCard from '@admin/components/infoCard.vue'
  import api from '@admin/api'

  export default {
    name: 'dashboard',
    components: {
      InfoCard
    },
    data () {
      return {
        infoData: {
          user_count: 0,
          recent_contest_count: 0,
          today_submission_count: 0,
          judge_server_count: 0,
          env: {}
        },
        activeNames: [1],
        session: {},
        loadingReleases: true,
        releases: []
      }
    },
    mounted () {
      api.getDashboardInfo().then(resp => {
        this.infoData = resp.data.data
      }, () => {
      })
      api.getSessions().then(resp => {
        this.parseSession(resp.data.data)
      }, () => {
      })
      api.getReleaseNotes().then(resp => {
        this.loadingReleases = false
        let data = resp.data.data
        if (!data) {
          return
        }
        let currentVersion = data.local_version
        data.update.forEach(release => {
          if (release.version > currentVersion) {
            release.new_version = true
          }
        })
        this.releases = data.update
      }, () => {
        this.loadingReleases = false
      })
    },
    methods: {
      parseSession (sessions) {
        let session = sessions[0]
        if (sessions.length > 1) {
          session = sessions.filter(s => !s.current_session).sort((a, b) => {
            return a.last_activity < b.last_activity
          })[0]
        }
        this.session = session
      }
    },
    computed: {
      ...mapGetters(['profile', 'user', 'isSuperAdmin']),
      cdn () {
        return this.infoData.env.STATIC_CDN_HOST
      },
      https () {
        return document.URL.slice(0, 5) === 'https'
      },
      forceHttps () {
        return this.infoData.env.FORCE_HTTPS
      },
      browser () {
        let b = browserDetector(this.session.user_agent)
        if (b.name && b.version) {
          return b.name + ' ' + b.version
        } else {
          return 'Unknown'
        }
      },
      os () {
        let b = browserDetector(this.session.user_agent)
        return b.os ? b.os : 'Unknown'
      }
    }
  }
</script>

<style lang="less">
  .admin-info {
    margin-bottom: 20px;
    &-name {
      font-size: 24px;
      font-weight: 700;
      margin-bottom: 10px;
      color: #409EFF;
    }
    .avatar {
      max-width: 100%;
    }
    .last-info {
      &-title {
        font-size: 16px;
      }
      &-body {
        .el-form-item {
          margin-bottom: 5px;
        }
      }
    }
  }

  .info-container {
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
    .info-item {
      flex: 1 0 auto;
      min-width: 200px;
      margin-bottom: 10px;
    }
  }

</style>


================================================
FILE: src/pages/admin/views/general/JudgeServer.vue
================================================
<template>
  <div class="view">
    <Panel :title="$t('m.Judge_Server_Token')">
      <code>{{ token }}</code>
    </Panel>
    <Panel :title="$t('m.Judge_Server_Info')">
      <el-table
        :data="servers"
        :default-expand-all="true"
        border>
        <el-table-column
          type="expand">
          <template slot-scope="props">
            <p>{{$t('m.IP')}}:
              <el-tag type="success">{{ props.row.ip }}</el-tag>&nbsp;&nbsp;
              {{$t('m.Judger_Version')}}:
              <el-tag type="success">{{ props.row.judger_version }}</el-tag>
            </p>
            <p>{{$t('m.Service_URL')}}: <code>{{ props.row.service_url }}</code></p>
            <p>{{$t('m.Last_Heartbeat')}}: {{ props.row.last_heartbeat | localtime}}</p>
            <p>{{$t('m.Create_Time')}}: {{ props.row.create_time | localtime }}</p>
          </template>
        </el-table-column>
        <el-table-column
          prop="status"
          label="Status">
          <template slot-scope="scope">
            <el-tag
              :type="scope.row.status === 'normal' ? 'success' : 'danger'">
              {{ scope.row.status === 'normal' ? 'Normal' : 'Abnormal' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
          prop="hostname"
          label="Hostname">
        </el-table-column>
        <el-table-column
          prop="task_number"
          label="Task Number">
        </el-table-column>
        <el-table-column
          prop="cpu_core"
          label="CPU Core">
        </el-table-column>
        <el-table-column
          prop="cpu_usage"
          label="CPU Usage">
          <template slot-scope="scope">{{ scope.row.cpu_usage }}%</template>
        </el-table-column>
        <el-table-column
          prop="memory_usage"
          label="Memory Usage">
          <template slot-scope="scope">{{ scope.row.memory_usage }}%</template>
        </el-table-column>
        <el-table-column label="Disabled">
          <template slot-scope="{row}">
            <el-switch v-model="row.is_disabled" @change="handleDisabledSwitch(row.id, row.is_disabled)"></el-switch>
          </template>
        </el-table-column>
        <el-table-column
          fixed="right"
          label="Options">
          <template slot-scope="scope">
            <icon-btn name="Delete" icon="trash" @click.native="deleteJudgeServer(scope.row.hostname)"></icon-btn>
          </template>
        </el-table-column>
      </el-table>
    </Panel>
  </div>
</template>

<script>
  import api from '../../api.js'

  export default {
    name: 'JudgeServer',
    data () {
      return {
        servers: [],
        token: '',
        intervalId: -1
      }
    },
    mounted () {
      this.refreshJudgeServerList()
      this.intervalId = setInterval(() => {
        this.refreshJudgeServerList()
      }, 5000)
    },
    methods: {
      refreshJudgeServerList () {
        api.getJudgeServer().then(res => {
          this.servers = res.data.data.servers
          this.token = res.data.data.token
        })
      },
      deleteJudgeServer (hostname) {
        this.$confirm('If you delete this judge server, it can\'t be used until next heartbeat', 'Warning', {
          confirmButtonText: 'Delete',
          cancelButtonText: 'Cancel',
          type: 'warning'
        }).then(() => {
          api.deleteJudgeServer(hostname).then(res =>
            this.refreshJudgeServerList()
          )
        }).catch(() => {
        })
      },
      handleDisabledSwitch (id, value) {
        let data = {
          id,
          is_disabled: value
        }
        api.updateJudgeServer(data).catch(() => {})
      }
    },
    beforeRouteLeave (to, from, next) {
      clearInterval(this.intervalId)
      next()
    }
  }
</script>


================================================
FILE: src/pages/admin/views/general/Login.vue
================================================
<template>
  <el-form :model="ruleForm2" :rules="rules2" ref="ruleForm2" label-position="left" label-width="0px"
           class="demo-ruleForm login-container">
    <h3 class="title">{{$t('m.Welcome_to_Login')}}</h3>
    <el-form-item prop="account">
      <el-input type="text" v-model="ruleForm2.account" auto-complete="off" :placeholder="$t('m.username')" @keyup.enter.native="handleLogin"></el-input>
    </el-form-item>
    <el-form-item prop="password">
      <el-input type="password" v-model="ruleForm2.password" auto-complete="off" :placeholder="$t('m.password')" @keyup.enter.native="handleLogin"></el-input>
    </el-form-item>
    <el-form-item style="width:100%;">
      <el-button type="primary" style="width:100%;" @click.native.prevent="handleLogin" :loading="logining">{{$t('m.GO')}}
      </el-button>
    </el-form-item>
  </el-form>
</template>

<script>
  import api from '../../api'

  export default {
    data () {
      return {
        logining: false,
        ruleForm2: {
          account: '',
          password: ''
        },
        rules2: {
          account: [
            {required: true, trigger: 'blur'}
          ],
          password: [
            {required: true, trigger: 'blur'}
          ]
        },
        checked: true
      }
    },
    methods: {
      handleLogin (ev) {
        this.$refs.ruleForm2.validate((valid) => {
          if (valid) {
            this.logining = true
            api.login(this.ruleForm2.account, this.ruleForm2.password).then(data => {
              this.logining = false
              this.$router.push({name: 'dashboard'})
            }, () => {
              this.logining = false
            })
          } else {
            this.$error('Please check the error fields')
          }
        })
      }
    }
  }
</script>

<style lang="less" scoped>
  .login-container {
    /*box-shadow: 0 0px 8px 0 rgba(0, 0, 0, 0.06), 0 1px 0px 0 rgba(0, 0, 0, 0.02);*/
    -webkit-border-radius: 5px;
    border-radius: 5px;
    -moz-border-radius: 5px;
    background-clip: padding-box;
    margin: 180px auto;
    width: 350px;
    padding: 35px 35px 15px 35px;
    background: #fff;
    border: 1px solid #eaeaea;
    box-shadow: 0 0 25px #cac6c6;
    .title {
      margin: 0px auto 40px auto;
      text-align: center;
      color: #505458;
    }
    .remember {
      margin: 0px 0px 35px 0px;
    }
  }
</style>


================================================
FILE: src/pages/admin/views/general/PruneTestCase.vue
================================================
<template>
  <div>
    <panel>
      <span slot="title">{{$t('m.Test_Case_Prune_Test_Case')}}
        <el-popover placement="right" trigger="hover">
          These test cases are not owned by any problem, you can clean them safely.
          <i slot="reference" class="el-icon-fa-question-circle import-user-icon"></i>
        </el-popover>
      </span>
      <el-table :data="data">
        <el-table-column
          label="Last Modified">
          <template slot-scope="{row}">
            {{row.create_time | timestampFormat }}
          </template>
        </el-table-column>
        <el-table-column
          prop="id"
          label="Test Case ID">
        </el-table-column>
        <el-table-column
          label="Option"
          fixed="right"
          width="200">
          <template slot-scope="{row}">
            <icon-btn name="Delete" icon="trash" @click.native="deleteTestCase(row.id)"></icon-btn>
          </template>
        </el-table-column>
      </el-table>
      <div class="panel-options" v-show="data.length > 0">
        <el-button type="warning" size="small"
                   :loading="loading"
                   icon="el-icon-fa-trash"
                   @click="deleteTestCase()">Delete All
        </el-button>
      </div>
    </panel>
  </div>
</template>

<script>
  import api from '@admin/api'
  import moment from 'moment'

  export default {
    name: 'prune-test-case',
    data () {
      return {
        data: [],
        loading: false
      }
    },
    mounted () {
      this.init()
    },
    methods: {
      init () {
        api.getInvalidTestCaseList().then(resp => {
          this.data = resp.data.data
        }, () => {
        })
      },
      deleteTestCase (id) {
        if (!id) {
          this.loading = true
        }
        api.pruneTestCase(id).then(resp => {
          this.loading = false
          this.init()
        })
      }
    },
    filters: {
      timestampFormat (value) {
        return moment.unix(value).format('YYYY-M-D  HH:mm:ss')
      }
    }
  }
</script>

<style>

</style>


================================================
FILE: src/pages/admin/views/general/User.vue
================================================
<template>
  <div class="view">
    <Panel :title="$t('m.User_User') ">
      <div slot="header">
        <el-row :gutter="20">
          <el-col :span="8">
            <el-button v-show="selectedUsers.length"
                       type="warning" icon="el-icon-fa-trash"
                       @click="deleteUsers(selectedUserIDs)">Delete
            </el-button>
          </el-col>
          <el-col :span="selectedUsers.length ? 16: 24">
            <el-input v-model="keyword" prefix-icon="el-icon-search" placeholder="Keywords"></el-input>
          </el-col>
        </el-row>
      </div>
      <el-table
        v-loading="loadingTable"
        element-loading-text="loading"
        @selection-change="handleSelectionChange"
        ref="table"
        :data="userList"
        style="width: 100%">
        <el-table-column type="selection" width="55"></el-table-column>

        <el-table-column prop="id" label="ID"></el-table-column>

        <el-table-column prop="username" label="Username"></el-table-column>

        <el-table-column prop="create_time" label="Create Time">
          <template slot-scope="scope">
            {{scope.row.create_time | localtime }}
          </template>
        </el-table-column>

        <el-table-column prop="last_login" label="Last Login">
          <template slot-scope="scope">
            {{scope.row.last_login | localtime }}
          </template>
        </el-table-column>

        <el-table-column prop="real_name" label="Real Name"></el-table-column>

        <el-table-column prop="email" label="Email"></el-table-column>

        <el-table-column prop="admin_type" label="User Type">
          <template slot-scope="scope">
            {{ scope.row.admin_type }}
          </template>
        </el-table-column>

        <el-table-column fixed="right" label="Option" width="200">
          <template slot-scope="{row}">
            <icon-btn name="Edit" icon="edit" @click.native="openUserDialog(row.id)"></icon-btn>
            <icon-btn name="Delete" icon="trash" @click.native="deleteUsers([row.id])"></icon-btn>
          </template>
        </el-table-column>
      </el-table>
      <div class="panel-options">
        <el-pagination
          class="page"
          layout="prev, pager, next"
          @current-change="currentChange"
          :page-size="pageSize"
          :total="total">
        </el-pagination>
      </div>
    </Panel>

    <Panel>
      <span slot="title">{{$t('m.Import_User')}}
        <el-popover placement="right" trigger="hover">
          <p>Only support csv file without headers, check the <a
            href="http://docs.onlinejudge.me/#/onlinejudge/guide/import_users">link</a> for details</p>
          <i slot="reference" class="el-icon-fa-question-circle import-user-icon"></i>
        </el-popover>
      </span>
      <el-upload v-if="!uploadUsers.length"
                 action=""
                 :show-file-list="false"
                 accept=".csv"
                 :before-upload="handleUsersCSV">
        <el-button size="small" icon="el-icon-fa-upload" type="primary">Choose File</el-button>
      </el-upload>
      <template v-else>
        <el-table :data="uploadUsersPage">
          <el-table-column label="Username">
            <template slot-scope="{row}">
              {{row[0]}}
            </template>
          </el-table-column>
          <el-table-column label="Password">
            <template slot-scope="{row}">
              {{row[1]}}
            </template>
          </el-table-column>
          <el-table-column label="Email">
            <template slot-scope="{row}">
              {{row[2]}}
            </template>
          </el-table-column>
          <el-table-column label="RealName">
            <template slot-scope="{row}">
              {{row[3]}}
            </template>
          </el-table-column>
        </el-table>
        <div class="panel-options">
          <el-button type="primary" size="small"
                     icon="el-icon-fa-upload"
                     @click="handleUsersUpload">Import All
          </el-button>
          <el-button type="warning" size="small"
                     icon="el-icon-fa-undo"
                     @click="handleResetData">Reset Data
          </el-button>
          <el-pagination
            class="page"
            layout="prev, pager, next"
            :page-size="uploadUsersPageSize"
            :current-page.sync="uploadUsersCurrentPage"
            :total="uploadUsers.length">
          </el-pagination>
        </div>
      </template>
    </Panel>

    <Panel :title="$t('m.Generate_User')">
      <el-form :model="formGenerateUser" ref="formGenerateUser">
        <el-row type="flex" justify="space-between">
          <el-col :span="4">
            <el-form-item label="Prefix" prop="prefix">
              <el-input v-model="formGenerateUser.prefix" placeholder="Prefix"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="Suffix" prop="suffix">
              <el-input v-model="formGenerateUser.suffix" placeholder="Suffix"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="Start Number" prop="number_from" required>
              <el-input-number v-model="formGenerateUser.number_from" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="End Number" prop="number_to" required>
              <el-input-number v-model="formGenerateUser.number_to" style="width: 100%"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="Password Length" prop="password_length" required>
              <el-input v-model="formGenerateUser.password_length"
                        placeholder="Password Length"></el-input>
            </el-form-item>
          </el-col>
        </el-row>

        <el-form-item>
          <el-button type="primary" @click="generateUser" icon="el-icon-fa-users" :loading="loadingGenerate">Generate & Export
          </el-button>
          <span class="userPreview" v-if="formGenerateUser.number_from && formGenerateUser.number_to &&
                                          formGenerateUser.number_from <= formGenerateUser.number_to">
            The usernames will be {{formGenerateUser.prefix + formGenerateUser.number_from + formGenerateUser.suffix}},
            <span v-if="formGenerateUser.number_from + 1 < formGenerateUser.number_to">
              {{formGenerateUser.prefix + (formGenerateUser.number_from + 1) + formGenerateUser.suffix + '...'}}
            </span>
            <span v-if="formGenerateUser.number_from + 1 <= formGenerateUser.number_to">
              {{formGenerateUser.prefix + formGenerateUser.number_to + formGenerateUser.suffix}}
            </span>
          </span>
        </el-form-item>
      </el-form>
    </Panel>
    <!--对话框-->
    <el-dialog :title="$t('m.User_Info')" :visible.sync="showUserDialog" :close-on-click-modal="false">
      <el-form :model="user" label-width="120px" label-position="left">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item :label="$t('m.User_Username')" required>
              <el-input v-model="user.username"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.User_Real_Name')" required>
              <el-input v-model="user.real_name"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.User_Email')" required>
              <el-input v-model="user.email"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.User_New_Password')">
              <el-input v-model="user.password"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.User_Type')">
              <el-select v-model="user.admin_type">
                <el-option label="Regular User" value="Regular User"></el-option>
                <el-option label="Admin" value="Admin"></el-option>
                <el-option label="Super Admin" value="Super Admin"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item :label="$t('m.Problem_Permission')">
              <el-select v-model="user.problem_permission" :disabled="user.admin_type!=='Admin'">
                <el-option label="None" value="None"></el-option>
                <el-option label="Own" value="Own"></el-option>
                <el-option label="All" value="All"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Two_Factor_Auth')">
              <el-switch
                v-model="user.two_factor_auth"
                :disabled="!user.real_tfa"
                active-color="#13ce66"
                inactive-color="#ff4949">
              </el-switch>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="Open Api">
              <el-switch
                v-model="user.open_api"
                active-color="#13ce66"
                inactive-color="#ff4949">
              </el-switch>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Is_Disabled')">
              <el-switch
                v-model="user.is_disabled">
              </el-switch>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <cancel @click.native="showUserDialog = false">Cancel</cancel>
        <save @click.native="saveUser()"></save>
      </span>
    </el-dialog>
  </div>
</template>

<script>
  import papa from 'papaparse'
  import api from '../../api.js'
  import utils from '@/utils/utils'

  export default {
    name: 'User',
    data () {
      return {
        // 一页显示的用户数
        pageSize: 10,
        // 用户总数
        total: 0,
        // 用户列表
        userList: [],
        uploadUsers: [],
        uploadUsersPage: [],
        uploadUsersCurrentPage: 1,
        uploadUsersPageSize: 15,
        // 搜索关键字
        keyword: '',
        // 是否显示用户对话框
        showUserDialog: false,
        // 当前用户model
        user: {},
        loadingTable: false,
        loadingGenerate: false,
        // 当前页码
        currentPage: 0,
        selectedUsers: [],
        formGenerateUser: {
          prefix: '',
          suffix: '',
          number_from: 0,
          number_to: 0,
          password_length: 8
        }
      }
    },
    mounted () {
      this.getUserList(1)
    },
    methods: {
      // 切换页码回调
      currentChange (page) {
        this.currentPage = page
        this.getUserList(page)
      },
      // 提交修改用户的信息
      saveUser () {
        api.editUser(this.user).then(res => {
          // 更新列表
          this.getUserList(this.currentPage)
        }).then(() => {
          this.showUserDialog = false
        }).catch(() => {
        })
      },
      // 打开用户对话框
      openUserDialog (id) {
        this.showUserDialog = true
        api.getUser(id).then(res => {
          this.user = res.data.data
          this.user.password = ''
          this.user.real_tfa = this.user.two_factor_auth
        })
      },
      // 获取用户列表
      getUserList (page) {
        this.loadingTable = true
        api.getUserList((page - 1) * this.pageSize, this.pageSize, this.keyword).then(res => {
          this.loadingTable = false
          this.total = res.data.data.total
          this.userList = res.data.data.results
        }, res => {
          this.loadingTable = false
        })
      },
      deleteUsers (ids) {
        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', {
          type: 'warning'
        }).then(() => {
          api.deleteUsers(ids.join(',')).then(res => {
            this.getUserList(this.currentPage)
          }).catch(() => {
            this.getUserList(this.currentPage)
          })
        }, () => {
        })
      },
      handleSelectionChange (val) {
        this.selectedUsers = val
      },
      generateUser () {
        this.$refs['formGenerateUser'].validate((valid) => {
          if (!valid) {
            this.$error('Please validate the error fields')
            return
          }
          this.loadingGenerate = true
          let data = Object.assign({}, this.formGenerateUser)
          api.generateUser(data).then(res => {
            this.loadingGenerate = false
            let url = '/admin/generate_user?file_id=' + res.data.data.file_id
            utils.downloadFile(url).then(() => {
              this.$alert('All users created successfully, the users sheets have downloaded to your disk.', 'Notice')
            })
            this.getUserList(1)
          }).catch(() => {
            this.loadingGenerate = false
          })
        })
      },
      handleUsersCSV (file) {
        papa.parse(file, {
          complete: (results) => {
            let data = results.data.filter(user => {
              return user[0] && user[1] && user[2] && user[3]
            })
            let delta = results.data.length - data.length
            if (delta > 0) {
              this.$warning(delta + ' users have been filtered due to empty value')
            }
            this.uploadUsersCurrentPage = 1
            this.uploadUsers = data
            this.uploadUsersPage = data.slice(0, this.uploadUsersPageSize)
          },
          error: (error) => {
            this.$error(error)
          }
        })
      },
      handleUsersUpload () {
        api.importUsers(this.uploadUsers).then(res => {
          this.getUserList(1)
          this.handleResetData()
        }).catch(() => {
        })
      },
      handleResetData () {
        this.uploadUsers = []
      }
    },
    computed: {
      selectedUserIDs () {
        let ids = []
        for (let user of this.selectedUsers) {
          ids.push(user.id)
        }
        return ids
      }
    },
    watch: {
      'keyword' () {
        this.currentChange(1)
      },
      'user.admin_type' () {
        if (this.user.admin_type === 'Super Admin') {
          this.user.problem_permission = 'All'
        } else if (this.user.admin_type === 'Regular User') {
          this.user.problem_permission = 'None'
        }
      },
      'uploadUsersCurrentPage' (page) {
        this.uploadUsersPage = this.uploadUsers.slice((page - 1) * this.uploadUsersPageSize, page * this.uploadUsersPageSize)
      }
    }
  }
</script>

<style scoped lang="less">
  .import-user-icon {
    color: #555555;
    margin-left: 4px;
  }

  .userPreview {
    padding-left: 10px;
  }

  .notification {
    p {
      margin: 0;
      text-align: left;
    }
  }
</style>


================================================
FILE: src/pages/admin/views/index.js
================================================
import Dashboard from './general/Dashboard.vue'
import Announcement from './general/Announcement.vue'
import User from './general/User.vue'
import Conf from './general/Conf.vue'
import JudgeServer from './general/JudgeServer.vue'
import PruneTestCase from './general/PruneTestCase.vue'
import Problem from './problem/Problem.vue'
import ProblemList from './problem/ProblemList.vue'
import ContestList from './contest/ContestList.vue'
import Contest from './contest/Contest.vue'
import Login from './general/Login.vue'
import Home from './Home.vue'
import ProblemImportOrExport from './problem/ImportAndExport.vue'

export {
  Announcement, User, Conf, JudgeServer, Problem, ProblemList, Contest,
  ContestList, Login, Home, PruneTestCase, Dashboard, ProblemImportOrExport
}


================================================
FILE: src/pages/admin/views/problem/AddPublicProblem.vue
================================================
<template>
  <div>
    <el-input
      v-model="keyword"
      placeholder="Keywords"
      prefix-icon="el-icon-search">
    </el-input>
    <el-table :data="problems" v-loading="loading">
      <el-table-column
        label="ID"
        width="100"
        prop="id">
      </el-table-column>
      <el-table-column
        label="DisplayID"
        width="200"
        prop="_id">
      </el-table-column>
      <el-table-column
        label="Title"
        prop="title">
      </el-table-column>
      <el-table-column
        label="option"
        align="center"
        width="100"
        fixed="right">
        <template slot-scope="{row}">
          <icon-btn icon="plus" name="Add the problem"
                    @click.native="handleAddProblem(row.id)"></icon-btn>
        </template>
      </el-table-column>
    </el-table>

    <el-pagination
      class="page"
      layout="prev, pager, next"
      @current-change="getPublicProblem"
      :page-size="limit"
      :total="total">
    </el-pagination>
  </div>
</template>
<script>
  import api from '@admin/api'

  export default {
    name: 'add-problem-from-public',
    props: ['contestID'],
    data () {
      return {
        page: 1,
        limit: 10,
        total: 0,
        loading: false,
        problems: [],
        contest: {},
        keyword: ''
      }
    },
    mounted () {
      api.getContest(this.contestID).then(res => {
        this.contest = res.data.data
        this.getPublicProblem()
      }).catch(() => {
      })
    },
    methods: {
      getPublicProblem (page) {
        this.loading = true
        let params = {
          keyword: this.keyword,
          offset: (page - 1) * this.limit,
          limit: this.limit,
          rule_type: this.contest.rule_type
        }
        api.getProblemList(params).then(res => {
          this.loading = false
          this.total = res.data.data.total
          this.problems = res.data.data.results
        }).catch(() => {
        })
      },
      handleAddProblem (problemID) {
        this.$prompt('Please input display id for the contest problem', 'confirm').then(({value}) => {
          let data = {
            problem_id: problemID,
            contest_id: this.contestID,
            display_id: value
          }
          api.addProblemFromPublic(data).then(() => {
            this.$emit('on-change')
          }, () => {
          })
        }, () => {
        })
      }
    },
    watch: {
      'keyword' () {
        this.getPublicProblem(this.page)
      }
    }
  }
</script>
<style scoped>
  .page {
    margin-top: 20px;
    text-align: right
  }

</style>


================================================
FILE: src/pages/admin/views/problem/ImportAndExport.vue
================================================
<template>
  <div>
    <div style="padding-bottom: 10px;">
    </div>
    <panel title="Export Problems (beta)">
      <div slot="header">
        <el-input
          v-model="keyword"
          prefix-icon="el-icon-search"
          placeholder="Keywords">
        </el-input>
      </div>
      <el-table :data="problems"
                v-loading="loadingProblems" @selection-change="handleSelectionChange">
        <el-table-column
          type="selection"
          width="60">
        </el-table-column>
        <el-table-column
          label="ID"
          width="100"
          prop="id">
        </el-table-column>
        <el-table-column
          label="DisplayID"
          width="200"
          prop="_id">
        </el-table-column>
        <el-table-column
          label="Title"
          prop="title">
        </el-table-column>
        <el-table-column
          prop="created_by.username"
          label="Author">
        </el-table-column>
        <el-table-column
          prop="create_time"
          label="Create Time">
          <template slot-scope="scope">
            {{scope.row.create_time | localtime }}
          </template>
        </el-table-column>
      </el-table>

      <div class="panel-options">
        <el-button type="primary" size="small" v-show="selected_problems.length"
                   @click="exportProblems" icon="el-icon-fa-arrow-down">Export
        </el-button>
        <el-pagination
          class="page"
          layout="prev, pager, next"
          @current-change="getProblems"
          :page-size="limit"
          :total="total">
        </el-pagination>
      </div>
    </panel>
    <panel title="Import QDUOJ Problems (beta)">
      <el-upload
        ref="QDU"
        action="/api/admin/import_problem"
        name="file"
        :file-list="fileList1"
        :show-file-list="true"
        :with-credentials="true"
        :limit="3"
        :on-change="onFile1Change"
        :auto-upload="false"
        :on-success="uploadSucceeded"
        :on-error="uploadFailed">
        <el-button size="small" type="primary" icon="el-icon-fa-upload" slot="trigger">Choose File</el-button>
        <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload('QDU')">Upload</el-button>
      </el-upload>
    </panel>

    <panel title="Import FPS Problems (beta)">
      <el-upload
        ref="FPS"
        action="/api/admin/import_fps"
        name="file"
        :file-list="fileList2"
        :show-file-list="true"
        :with-credentials="true"
        :limit="3"
        :on-change="onFile2Change"
        :auto-upload="false"
        :on-success="uploadSucceeded"
        :on-error="uploadFailed">
        <el-button size="small" type="primary" icon="el-icon-fa-upload" slot="trigger">Choose File</el-button>
        <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload('FPS')">Upload</el-button>
      </el-upload>
    </panel>
  </div>
</template>
<script>
  import api from '@admin/api'
  import utils from '@/utils/utils'

  export default {
    name: 'import_and_export',
    data () {
      return {
        fileList1: [],
        fileList2: [],
        page: 1,
        limit: 10,
        total: 0,
        loadingProblems: false,
        loadingImporting: false,
        keyword: '',
        problems: [],
        selected_problems: []
      }
    },
    mounted () {
      this.getProblems()
    },
    methods: {
      handleSelectionChange (val) {
        this.selected_problems = val
      },
      getProblems (page = 1) {
        let params = {
          keyword: this.keyword,
          offset: (page - 1) * this.limit,
          limit: this.limit
        }
        this.loadingProblems = true
        api.getProblemList(params).then(res => {
          this.problems = res.data.data.results
          this.total = res.data.data.total
          this.loadingProblems = false
        })
      },
      exportProblems () {
        let params = []
        for (let p of this.selected_problems) {
          params.push('problem_id=' + p.id)
        }
        let url = '/admin/export_problem?' + params.join('&')
        utils.downloadFile(url)
      },
      submitUpload (ref) {
        this.$refs[ref].submit()
      },
      onFile1Change (file, fileList) {
        this.fileList1 = fileList.slice(-1)
      },
      onFile2Change (file, fileList) {
        this.fileList2 = fileList.slice(-1)
      },
      uploadSucceeded (response) {
        if (response.error) {
          this.$error(response.data)
        } else {
          this.$success('Successfully imported ' + response.data.import_count + ' problems')
          this.getProblems()
        }
      },
      uploadFailed () {
        this.$error('Upload failed')
      }
    },
    watch: {
      'keyword' () {
        this.getProblems()
      }
    }
  }
</script>

<style scoped lang="less">

</style>


================================================
FILE: src/pages/admin/views/problem/Problem.vue
================================================
<template>
  <div class="problem">

    <Panel :title="title">
      <el-form ref="form" :model="problem" :rules="rules" label-position="top" label-width="70px">
        <el-row :gutter="20">
          <el-col :span="6">
            <el-form-item prop="_id" :label="$t('m.Display_ID')"
                          :required="this.routeName === 'create-contest-problem' || this.routeName === 'edit-contest-problem'">
              <el-input :placeholder="$t('m.Display_ID')" v-model="problem._id"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="18">
            <el-form-item prop="title" :label="$t('m.Title')" required>
              <el-input :placeholder="$t('m.Title')" v-model="problem.title"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item prop="description" :label="$t('m.Description')" required>
              <Simditor v-model="problem.description"></Simditor>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item prop="input_description" :label="$t('m.Input_Description')" required>
              <Simditor v-model="problem.input_description"></Simditor>
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item prop="output_description" :label="$t('m.Output_Description')" required>
              <Simditor v-model="problem.output_description"></Simditor>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item :label="$t('m.Time_Limit') + ' (ms)' " required>
              <el-input type="Number" :placeholder="$t('m.Time_Limit')" v-model="problem.time_limit"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Memory_limit') + ' (MB)' " required>
              <el-input type="Number" :placeholder="$t('m.Memory_limit')" v-model="problem.memory_limit"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Difficulty')">
              <el-select class="difficulty-select" size="small" :placeholder="$t('m.Difficulty')" v-model="problem.difficulty">
                <el-option :label="$t('m.Low')" value="Low"></el-option>
                <el-option :label="$t('m.Mid')" value="Mid"></el-option>
                <el-option :label="$t('m.High')" value="High"></el-option>
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item :label="$t('m.Visible')">
              <el-switch
                v-model="problem.visible"
                active-text=""
                inactive-text="">
              </el-switch>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item :label="$t('m.ShareSubmission')">
              <el-switch
                v-model="problem.share_submission"
                active-text=""
                inactive-text="">
              </el-switch>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Tag')" :error="error.tags" required>
              <span class="tags">
                <el-tag
                  v-for="tag in problem.tags"
                  :closable="true"
                  :close-transition="false"
                  :key="tag"
                  type="success"
                  @close="closeTag(tag)"
                >{{tag}}</el-tag>
              </span>
              <el-autocomplete
                v-if="inputVisible"
                size="mini"
                class="input-new-tag"
                popper-class="problem-tag-poper"
                v-model="tagInput"
                :trigger-on-focus="false"
                @keyup.enter.native="addTag"
                @select="addTag"
                :fetch-suggestions="querySearch">
              </el-autocomplete>
              <el-button class="button-new-tag" v-else size="small" @click="inputVisible = true">+ {{$t('m.New_Tag')}}</el-button>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item :label="$t('m.Languages')" :error="error.languages" required>
              <el-checkbox-group v-model="problem.languages">
                <el-tooltip class="spj-radio" v-for="lang in allLanguage.languages" :key="'spj'+lang.name" effect="dark"
                            :content="lang.description" placement="top-start">
                  <el-checkbox :label="lang.name"></el-checkbox>
                </el-tooltip>
              </el-checkbox-group>
            </el-form-item>
          </el-col>
        </el-row>
        <div>
          <el-form-item v-for="(sample, index) in problem.samples" :key="'sample'+index">
            <Accordion :title="'Sample' + (index + 1)">
              <el-button type="warning" size="small" icon="el-icon-delete" slot="header" @click="deleteSample(index)">
                Delete
              </el-button>
              <el-row :gutter="20">
                <el-col :span="12">
                  <el-form-item :label="$t('m.Input_Samples')" required>
                    <el-input
                      :rows="5"
                      type="textarea"
                      :placeholder="$t('m.Input_Samples')"
                      v-model="sample.input">
                    </el-input>
                  </el-form-item>
                </el-col>
                <el-col :span="12">
                  <el-form-item :label="$t('m.Output_Samples')" required>
                    <el-input
                      :rows="5"
                      type="textarea"
                      :placeholder="$t('m.Output_Samples')"
                      v-model="sample.output">
                    </el-input>
                  </el-form-item>
                </el-col>
              </el-row>
            </Accordion>
          </el-form-item>
        </div>
        <div class="add-sample-btn">
          <button type="button" class="add-samples" @click="addSample()"><i class="el-icon-plus"></i>{{$t('m.Add_Sample')}}
          </button>
        </div>
        <el-form-item style="margin-top: 20px" :label="$t('m.Hint')">
          <Simditor v-model="problem.hint" placeholder=""></Simditor>
        </el-form-item>
        <el-form-item :label="$t('m.Code_Template')">
          <el-row>
            <el-col :span="24" v-for="(v, k) in template" :key="'template'+k">
              <el-form-item>
                <el-checkbox v-model="v.checked">{{ k }}</el-checkbox>
                <div v-if="v.checked">
                  <code-mirror v-model="v.code" :mode="v.mode"></code-mirror>
                </div>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form-item>
        <el-form-item :label="$t('m.Special_Judge')" :error="error.spj">
          <el-col :span="24">
            <el-checkbox v-model="problem.spj" @click.native.prevent="switchSpj()">{{$t('m.Use_Special_Judge')}}</el-checkbox>
          </el-col>
        </el-form-item>
        <el-form-item v-if="problem.spj">
          <Accordion :title="$t('m.Special_Judge_Code')">
            <template slot="header">
              <span>{{$t('m.SPJ_language')}}</span>
              <el-radio-group v-model="problem.spj_language">
                <el-tooltip class="spj-radio" v-for="lang in allLanguage.spj_languages" :key="lang.name" effect="dark"
                            :content="lang.description" placement="top-start">
                  <el-radio :label="lang.name">{{ lang.name }}</el-radio>
                </el-tooltip>
              </el-radio-group>
              <el-button type="primary" size="small" icon="el-icon-fa-random" @click="compileSPJ"
                         :loading="loadingCompile">
                {{$t('m.Compile')}}
              </el-button>
            </template>
            <code-mirror v-model="problem.spj_code" :mode="spjMode"></code-mirror>
          </Accordion>
        </el-form-item>
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item :label="$t('m.Type')">
              <el-radio-group v-model="problem.rule_type" :disabled="disableRuleType">
                <el-radio label="ACM">ACM</el-radio>
                <el-radio label="OI">OI</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item :label="$t('m.TestCase')" :error="error.testcase">
              <el-upload
                action="/api/admin/test_case"
                name="file"
                :data="{spj: problem.spj}"
                :show-file-list="true"
                :on-success="uploadSucceeded"
                :on-error="uploadFailed">
                <el-button size="small" type="primary" icon="el-icon-fa-upload">Choose File</el-button>
              </el-upload>
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item :label="$t('m.IOMode')">
              <el-radio-group v-model="problem.io_mode.io_mode">
                <el-radio label="Standard IO">Standard IO</el-radio>
                <el-radio label="File IO">File IO</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>

          <el-col :span="4" v-if="problem.io_mode.io_mode == 'File IO'">
            <el-form-item :label="$t('m.InputFileName')" required>
              <el-input type="text" v-model="problem.io_mode.input"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="4" v-if="problem.io_mode.io_mode == 'File IO'">
            <el-form-item :label="$t('m.OutputFileName')" required>
              <el-input type="text" v-model="problem.io_mode.output"></el-input>
            </el-form-item>
          </el-col>

          <el-col :span="24">
            <el-table
              :data="problem.test_case_score"
              style="width: 100%">
              <el-table-column
                prop="input_name"
                :label="$t('m.Input')">
              </el-table-column>
              <el-table-column
                prop="output_name"
                :label="$t('m.Output')">
              </el-table-column>
              <el-table-column
                prop="score"
                :label="$t('m.Score')">
                <template slot-scope="scope">
                  <el-input
                    size="small"
                    :placeholder="$t('m.Score')"
                    v-model="scope.row.score"
                    :disabled="problem.rule_type !== 'OI'">
                  </el-input>
                </template>
              </el-table-column>
            </el-table>
          </el-col>
        </el-row>

        <el-form-item :label="$t('m.Source')">
          <el-input :placeholder="$t('m.Source')" v-model="problem.source"></el-input>
        </el-form-item>
        <save @click.native="submit()">Save</save>
      </el-form>
    </Panel>
  </div>
</template>

<script>
  import Simditor from '../../components/Simditor'
  import Accordion from '../../components/Accordion'
  import CodeMirror from '../../components/CodeMirror'
  import api from '../../api'

  export default {
    name: 'Problem',
    components: {
      Simditor,
      Accordion,
      CodeMirror
    },
    data () {
      return {
        rules: {
          _id: {required: true, message: 'Display ID is required', trigger: 'blur'},
          title: {required: true, message: 'Title is required', trigger: 'blur'},
          input_description: {required: true, message: 'Input Description is required', trigger: 'blur'},
          output_description: {required: true, message: 'Output Description is required', trigger: 'blur'}
        },
        loadingCompile: false,
        mode: '',
        contest: {},
        problem: {
          languages: [],
          io_mode: {'io_mode': 'Standard IO', 'input': 'input.txt', 'output': 'output.txt'}
        },
        reProblem: {
          languages: [],
          io_mode: {'io_mode': 'Standard IO', 'input': 'input.txt', 'output': 'output.txt'}
        },
        testCaseUploaded: false,
        allLanguage: {},
        inputVisible: false,
        tagInput: '',
        template: {},
        title: '',
        spjMode: '',
        disableRuleType: false,
        routeName: '',
        error: {
          tags: '',
          spj: '',
          languages: '',
          testCase: ''
        }
      }
    },
    mounted () {
      this.routeName = this.$route.name
      if (this.routeName === 'edit-problem' || this.routeName === 'edit-contest-problem') {
        this.mode = 'edit'
      } else {
        this.mode = 'add'
      }
      api.getLanguages().then(res => {
        this.problem = this.reProblem = {
          _id: '',
          title: '',
          description: '',
          input_description: '',
          output_description: '',
          time_limit: 1000,
          memory_limit: 256,
          difficulty: 'Low',
          visible: true,
          share_submission: false,
          tags: [],
          languages: [],
          template: {},
          samples: [{input: '', output: ''}],
          spj: false,
          spj_language: '',
          spj_code: '',
          spj_compile_ok: false,
          test_case_id: '',
          test_case_score: [],
          rule_type: 'ACM',
          hint: '',
          source: '',
          io_mode: {'io_mode': 'Standard IO', 'input': 'input.txt', 'output': 'output.txt'}
        }
        let contestID = this.$route.params.contestId
        if (contestID) {
          this.problem.contest_id = this.reProblem.contest_id = contestID
          this.disableRuleType = true
          api.getContest(contestID).then(res => {
            this.problem.rule_type = this.reProblem.rule_type = res.data.data.rule_type
            this.contest = res.data.data
          })
        }

        this.problem.spj_language = 'C'

        let allLanguage = res.data.data
        this.allLanguage = allLanguage

        // get problem after getting languages list to avoid find undefined value in `watch problem.languages`
        if (this.mode === 'edit') {
          this.title = this.$i18n.t('m.Edit_Problem')
          let funcName = {'edit-problem': 'getProblem', 'edit-contest-problem': 'getContestProblem'}[this.routeName]
          api[funcName](this.$route.params.problemId).then(problemRes => {
            let data = problemRes.data.data
            if (!data.spj_code) {
              data.spj_code = ''
            }
            data.spj_language = data.spj_language || 'C'
            this.problem = data
            this.testCaseUploaded = true
          })
        } else {
          this.title = this.$i18n.t('m.Add_Problem')
          for (let item of allLanguage.languages) {
            this.problem.languages.push(item.name)
          }
        }
      })
    },
    watch: {
      '$route' () {
        this.$refs.form.resetFields()
        this.problem = this.reProblem
      },
      'problem.languages' (newVal) {
        let data = {}
        // use deep copy to avoid infinite loop
        let languages = JSON.parse(JSON.stringify(newVal)).sort()
        for (let item of languages) {
          if (this.template[item] === undefined) {
            let langConfig = this.allLanguage.languages.find(lang => {
              return lang.name === item
            })
            if (this.problem.template[item] === undefined) {
              data[item] = {checked: false, code: langConfig.config.template, mode: langConfig.content_type}
            } else {
              data[item] = {checked: true, code: this.problem.template[item], mode: langConfig.content_type}
            }
          } else {
            data[item] = this.template[item]
          }
        }
        this.template = data
      },
      'problem.spj_language' (newVal) {
        this.spjMode = this.allLanguage.spj_languages.find(item => {
          return item.name === this.problem.spj_language
        }).content_type
      }
    },
    methods: {
      switchSpj () {
        if (this.testCaseUploaded) {
          this.$confirm('If you change problem judge method, you need to re-upload test cases', 'Warning', {
            confirmButtonText: 'Yes',
            cancelButtonText: 'Cancel',
            type: 'warning'
          }).then(() => {
            this.problem.spj = !this.problem.spj
            this.resetTestCase()
          }).catch(() => {
          })
        } else {
          this.problem.spj = !this.problem.spj
        }
      },
      querySearch (queryString, cb) {
        api.getProblemTagList({ keyword: queryString }).then(res => {
          let tagList = []
          for (let tag of res.data.data) {
            tagList.push({value: tag.name})
          }
          cb(tagList)
        }).catch(() => {
        })
      },
      resetTestCase () {
        this.testCaseUploaded = false
        this.problem.test_case_score = []
        this.problem.test_case_id = ''
      },
      addTag () {
        let inputValue = this.tagInput
        if (inputValue) {
          this.problem.tags.push(inputValue)
        }
        this.inputVisible = false
        this.tagInput = ''
      },
      closeTag (tag) {
        this.problem.tags.splice(this.problem.tags.indexOf(tag), 1)
      },
      addSample () {
        this.problem.samples.push({input: '', output: ''})
      },
      deleteSample (index) {
        this.problem.samples.splice(index, 1)
      },
      uploadSucceeded (response) {
        if (response.error) {
          this.$error(response.data)
          return
        }
        let fileList = response.data.info
        for (let file of fileList) {
          file.score = (100 / fileList.length).toFixed(0)
          if (!file.output_name && this.problem.spj) {
            file.output_name = '-'
          }
        }
        this.problem.test_case_score = fileList
        this.testCaseUploaded = true
        this.problem.test_case_id = response.data.id
      },
      uploadFailed () {
        this.$error('Upload failed')
      },
      compileSPJ () {
        let data = {
          id: this.problem.id,
          spj_code: this.problem.spj_code,
          spj_language: this.problem.spj_language
        }
        this.loadingCompile = true
        api.compileSPJ(data).then(res => {
          this.loadingCompile = false
          this.problem.spj_compile_ok = true
          this.error.spj = ''
        }, err => {
          this.loadingCompile = false
          this.problem.spj_compile_ok = false
          const h = this.$createElement
          this.$msgbox({
            title: 'Compile Error',
            type: 'error',
            message: h('pre', err.data.data),
            showCancelButton: false,
            closeOnClickModal: false,
            customClass: 'dialog-compile-error'
          })
        })
      },
      submit () {
        if (!this.problem.samples.length) {
          this.$error('Sample is required')
          return
        }
        for (let sample of this.problem.samples) {
          if (!sample.input || !sample.output) {
            this.$error('Sample input and output is required')
            return
          }
        }
        if (!this.problem.tags.length) {
          this.error.tags = 'Please add at least one tag'
          this.$error(this.error.tags)
          return
        }
        if (this.problem.spj) {
          if (!this.problem.spj_code) {
            this.error.spj = 'Spj code is required'
            this.$error(this.error.spj)
          } else if (!this.problem.spj_compile_ok) {
            this.error.spj = 'SPJ code has not been successfully compiled'
          }
          if (this.error.spj) {
            this.$error(this.error.spj)
            return
          }
        }
        if (!this.problem.languages.length) {
          this.error.languages = 'Please choose at least one language for problem'
          this.$error(this.error.languages)
          return
        }
        if (!this.testCaseUploaded) {
          this.error.testCase = 'Test case is not uploaded yet'
          this.$error(this.error.testCase)
          return
        }
        if (this.problem.rule_type === 'OI') {
          for (let item of this.problem.test_case_score) {
   
Download .txt
gitextract_o9t9gx4q/

├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .postcssrc.js
├── .travis.yml
├── LICENSE
├── README.md
├── build/
│   ├── build.js
│   ├── check-versions.js
│   ├── dev-client.js
│   ├── dev-server.js
│   ├── utils.js
│   ├── vue-loader.conf.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   ├── webpack.dll.conf.js
│   └── webpack.prod.conf.js
├── config/
│   ├── dev.env.js
│   ├── index.js
│   └── prod.env.js
├── deploy/
│   ├── Dockerfile
│   ├── nginx.conf
│   ├── run.sh
│   └── sentry_release.sh
├── package.json
├── src/
│   ├── i18n/
│   │   ├── admin/
│   │   │   ├── en-US.js
│   │   │   ├── zh-CN.js
│   │   │   └── zh-TW.js
│   │   ├── index.js
│   │   └── oj/
│   │       ├── en-US.js
│   │       ├── zh-CN.js
│   │       └── zh-TW.js
│   ├── pages/
│   │   ├── admin/
│   │   │   ├── App.vue
│   │   │   ├── api.js
│   │   │   ├── components/
│   │   │   │   ├── Accordion.vue
│   │   │   │   ├── CodeMirror.vue
│   │   │   │   ├── KatexEditor.vue
│   │   │   │   ├── Panel.vue
│   │   │   │   ├── ScreenFull.vue
│   │   │   │   ├── SideMenu.vue
│   │   │   │   ├── Simditor.vue
│   │   │   │   ├── TopNav.vue
│   │   │   │   ├── btn/
│   │   │   │   │   ├── Cancel.vue
│   │   │   │   │   ├── IconBtn.vue
│   │   │   │   │   └── Save.vue
│   │   │   │   ├── infoCard.vue
│   │   │   │   └── simditor-file-upload.js
│   │   │   ├── index.html
│   │   │   ├── index.js
│   │   │   ├── router.js
│   │   │   ├── style.less
│   │   │   └── views/
│   │   │       ├── Home.vue
│   │   │       ├── contest/
│   │   │       │   ├── Contest.vue
│   │   │       │   └── ContestList.vue
│   │   │       ├── general/
│   │   │       │   ├── Announcement.vue
│   │   │       │   ├── Conf.vue
│   │   │       │   ├── Dashboard.vue
│   │   │       │   ├── JudgeServer.vue
│   │   │       │   ├── Login.vue
│   │   │       │   ├── PruneTestCase.vue
│   │   │       │   └── User.vue
│   │   │       ├── index.js
│   │   │       └── problem/
│   │   │           ├── AddPublicProblem.vue
│   │   │           ├── ImportAndExport.vue
│   │   │           ├── Problem.vue
│   │   │           └── ProblemList.vue
│   │   └── oj/
│   │       ├── App.vue
│   │       ├── api.js
│   │       ├── components/
│   │       │   ├── CodeMirror.vue
│   │       │   ├── Highlight.vue
│   │       │   ├── NavBar.vue
│   │       │   ├── Pagination.vue
│   │       │   ├── Panel.vue
│   │       │   ├── mixins/
│   │       │   │   ├── emitter.js
│   │       │   │   ├── form.js
│   │       │   │   ├── index.js
│   │       │   │   └── problem.js
│   │       │   └── verticalMenu/
│   │       │       ├── verticalMenu-item.vue
│   │       │       └── verticalMenu.vue
│   │       ├── index.html
│   │       ├── index.js
│   │       ├── router/
│   │       │   ├── index.js
│   │       │   └── routes.js
│   │       └── views/
│   │           ├── contest/
│   │           │   ├── ContestDetail.vue
│   │           │   ├── ContestList.vue
│   │           │   ├── children/
│   │           │   │   ├── ACMContestRank.vue
│   │           │   │   ├── ACMHelper.vue
│   │           │   │   ├── ContestProblemList.vue
│   │           │   │   ├── ContestRank.vue
│   │           │   │   ├── OIContestRank.vue
│   │           │   │   └── contestRankMixin.js
│   │           │   └── index.js
│   │           ├── general/
│   │           │   ├── 404.vue
│   │           │   ├── Announcements.vue
│   │           │   └── Home.vue
│   │           ├── help/
│   │           │   ├── About.vue
│   │           │   └── FAQ.vue
│   │           ├── index.js
│   │           ├── problem/
│   │           │   ├── Problem.vue
│   │           │   ├── ProblemList.vue
│   │           │   └── chartData.js
│   │           ├── rank/
│   │           │   ├── ACMRank.vue
│   │           │   └── OIRank.vue
│   │           ├── setting/
│   │           │   ├── Settings.vue
│   │           │   ├── children/
│   │           │   │   ├── AccountSetting.vue
│   │           │   │   ├── ProfileSetting.vue
│   │           │   │   └── SecuritySetting.vue
│   │           │   └── index.js
│   │           ├── submission/
│   │           │   ├── SubmissionDetails.vue
│   │           │   └── SubmissionList.vue
│   │           └── user/
│   │               ├── ApplyResetPassword.vue
│   │               ├── Login.vue
│   │               ├── Logout.vue
│   │               ├── Register.vue
│   │               ├── ResetPassword.vue
│   │               └── UserHome.vue
│   ├── plugins/
│   │   ├── highlight.js
│   │   └── katex.js
│   ├── store/
│   │   ├── index.js
│   │   ├── modules/
│   │   │   ├── contest.js
│   │   │   └── user.js
│   │   └── types.js
│   ├── styles/
│   │   ├── common.less
│   │   ├── index.less
│   │   ├── iview-custom.less
│   │   └── markdown.less
│   └── utils/
│       ├── constants.js
│       ├── filters.js
│       ├── sentry.js
│       ├── storage.js
│       ├── time.js
│       └── utils.js
└── static/
    └── css/
        └── loader.css
Download .txt
SYMBOL INDEX (178 symbols across 24 files)

FILE: build/check-versions.js
  function exec (line 6) | function exec (cmd) {

FILE: build/utils.js
  function generateLoaders (line 25) | function generateLoaders (loader, loaderOptions) {

FILE: build/webpack.base.conf.js
  function resolve (line 10) | function resolve (dir) {
  function getEntries (line 14) | function getEntries () {

FILE: build/webpack.dll.conf.js
  function resolve (line 9) | function resolve (dir) {
  constant NODE_ENV (line 13) | const NODE_ENV = utils.getNodeEnv()

FILE: src/pages/admin/api.js
  method login (line 13) | login (username, password) {
  method logout (line 21) | logout () {
  method getProfile (line 24) | getProfile () {
  method getAnnouncementList (line 28) | getAnnouncementList (offset, limit) {
  method deleteAnnouncement (line 38) | deleteAnnouncement (id) {
  method updateAnnouncement (line 46) | updateAnnouncement (data) {
  method createAnnouncement (line 52) | createAnnouncement (data) {
  method getUserList (line 58) | getUserList (offset, limit, keyword) {
  method getUser (line 68) | getUser (id) {
  method editUser (line 76) | editUser (data) {
  method deleteUsers (line 81) | deleteUsers (id) {
  method importUsers (line 88) | importUsers (users) {
  method generateUser (line 95) | generateUser (data) {
  method getLanguages (line 100) | getLanguages () {
  method getSMTPConfig (line 103) | getSMTPConfig () {
  method createSMTPConfig (line 106) | createSMTPConfig (data) {
  method editSMTPConfig (line 111) | editSMTPConfig (data) {
  method testSMTPConfig (line 116) | testSMTPConfig (email) {
  method getWebsiteConfig (line 123) | getWebsiteConfig () {
  method editWebsiteConfig (line 126) | editWebsiteConfig (data) {
  method getJudgeServer (line 131) | getJudgeServer () {
  method deleteJudgeServer (line 134) | deleteJudgeServer (hostname) {
  method updateJudgeServer (line 141) | updateJudgeServer (data) {
  method getInvalidTestCaseList (line 146) | getInvalidTestCaseList () {
  method pruneTestCase (line 149) | pruneTestCase (id) {
  method createContest (line 156) | createContest (data) {
  method getContest (line 161) | getContest (id) {
  method editContest (line 168) | editContest (data) {
  method getContestList (line 173) | getContestList (offset, limit, keyword) {
  method getContestAnnouncementList (line 182) | getContestAnnouncementList (contestID) {
  method createContestAnnouncement (line 189) | createContestAnnouncement (data) {
  method deleteContestAnnouncement (line 194) | deleteContestAnnouncement (id) {
  method updateContestAnnouncement (line 201) | updateContestAnnouncement (data) {
  method getProblemTagList (line 206) | getProblemTagList (params) {
  method compileSPJ (line 211) | compileSPJ (data) {
  method createProblem (line 216) | createProblem (data) {
  method editProblem (line 221) | editProblem (data) {
  method deleteProblem (line 226) | deleteProblem (id) {
  method getProblem (line 233) | getProblem (id) {
  method getProblemList (line 240) | getProblemList (params) {
  method getContestProblemList (line 246) | getContestProblemList (params) {
  method getContestProblem (line 252) | getContestProblem (id) {
  method createContestProblem (line 259) | createContestProblem (data) {
  method editContestProblem (line 264) | editContestProblem (data) {
  method deleteContestProblem (line 269) | deleteContestProblem (id) {
  method makeContestProblemPublic (line 276) | makeContestProblemPublic (data) {
  method addProblemFromPublic (line 281) | addProblemFromPublic (data) {
  method getReleaseNotes (line 286) | getReleaseNotes () {
  method getDashboardInfo (line 289) | getDashboardInfo () {
  method getSessions (line 292) | getSessions () {
  method exportProblems (line 295) | exportProblems (data) {
  function ajax (line 309) | function ajax (url, method, options) {

FILE: src/pages/admin/components/simditor-file-upload.js
  function ctor (line 13) | function ctor() {
  function UploadFile (line 40) | function UploadFile() {

FILE: src/pages/oj/api.js
  method getWebsiteConf (line 11) | getWebsiteConf (params) {
  method getAnnouncementList (line 16) | getAnnouncementList (offset, limit) {
  method login (line 25) | login (data) {
  method checkUsernameOrEmail (line 30) | checkUsernameOrEmail (username, email) {
  method register (line 39) | register (data) {
  method logout (line 44) | logout () {
  method getCaptcha (line 47) | getCaptcha () {
  method getUserInfo (line 50) | getUserInfo (username = undefined) {
  method updateProfile (line 57) | updateProfile (profile) {
  method freshDisplayID (line 62) | freshDisplayID (userID) {
  method twoFactorAuth (line 69) | twoFactorAuth (method, data) {
  method tfaRequiredCheck (line 74) | tfaRequiredCheck (username) {
  method getSessions (line 81) | getSessions () {
  method deleteSession (line 84) | deleteSession (sessionKey) {
  method applyResetPassword (line 91) | applyResetPassword (data) {
  method resetPassword (line 96) | resetPassword (data) {
  method changePassword (line 101) | changePassword (data) {
  method changeEmail (line 106) | changeEmail (data) {
  method getLanguages (line 111) | getLanguages () {
  method getProblemTagList (line 114) | getProblemTagList () {
  method getProblemList (line 117) | getProblemList (offset, limit, searchParams) {
  method pickone (line 132) | pickone () {
  method getProblem (line 135) | getProblem (problemID) {
  method getContestList (line 142) | getContestList (offset, limit, searchParams) {
  method getContest (line 158) | getContest (id) {
  method getContestAccess (line 165) | getContestAccess (contestID) {
  method checkContestPassword (line 172) | checkContestPassword (contestID, password) {
  method getContestAnnouncementList (line 180) | getContestAnnouncementList (contestId) {
  method getContestProblemList (line 187) | getContestProblemList (contestId) {
  method getContestProblem (line 194) | getContestProblem (problemID, contestID) {
  method submitCode (line 202) | submitCode (data) {
  method getSubmissionList (line 207) | getSubmissionList (offset, limit, params) {
  method getContestSubmissionList (line 214) | getContestSubmissionList (offset, limit, params) {
  method getSubmission (line 221) | getSubmission (id) {
  method submissionExists (line 228) | submissionExists (problemID) {
  method submissionRejudge (line 235) | submissionRejudge (id) {
  method updateSubmission (line 242) | updateSubmission (data) {
  method getUserRank (line 247) | getUserRank (offset, limit, rule = 'acm') {
  method getContestRank (line 257) | getContestRank (params) {
  method getACMACInfo (line 262) | getACMACInfo (params) {
  method updateACInfoCheckedStatus (line 267) | updateACInfoCheckedStatus (data) {
  function ajax (line 281) | function ajax (url, method, options) {

FILE: src/pages/oj/components/mixins/emitter.js
  function broadcast (line 1) | function broadcast (componentName, eventName, params) {
  method dispatch (line 16) | dispatch (componentName, eventName, params) {
  method broadcast (line 31) | broadcast (componentName, eventName, params) {

FILE: src/pages/oj/components/mixins/form.js
  method data (line 4) | data () {
  method validateForm (line 10) | validateForm (formName) {
  method getCaptchaSrc (line 21) | getCaptchaSrc () {

FILE: src/pages/oj/components/mixins/problem.js
  method data (line 4) | data () {
  method getACRate (line 10) | getACRate (ACCount, TotalCount) {
  method addStatusColumn (line 13) | addStatusColumn (tableColumns, dataProblems) {

FILE: src/pages/oj/router/index.js
  method scrollBehavior (line 13) | scrollBehavior (to, from, savedPosition) {

FILE: src/pages/oj/views/contest/children/contestRankMixin.js
  method getContestRankData (line 12) | getContestRankData (page = 1, refresh = false) {
  method handleAutoRefresh (line 34) | handleAutoRefresh (status) {
  method get (line 52) | get () {
  method set (line 55) | set (value) {
  method get (line 60) | get () {
  method set (line 63) | set (value) {
  method get (line 74) | get () {
  method set (line 77) | set (value) {
  method get (line 94) | get () {
  method set (line 97) | set (value) {
  method get (line 102) | get () {
  method set (line 105) | set (value) {
  method refreshDisabled (line 109) | refreshDisabled () {
  method beforeDestroy (line 113) | beforeDestroy () {

FILE: src/pages/oj/views/problem/chartData.js
  function getItemColor (line 11) | function getItemColor (obj) {

FILE: src/plugins/highlight.js
  method install (line 12) | install (Vue, options) {

FILE: src/plugins/katex.js
  function _ (line 5) | function _ () {
  function render (line 19) | function render (el, binding) {

FILE: src/store/index.js
  method 'website' (line 20) | 'website' (state) {
  method 'modalStatus' (line 23) | 'modalStatus' (state) {
  method [types.UPDATE_WEBSITE_CONF] (line 29) | [types.UPDATE_WEBSITE_CONF] (state, payload) {
  method [types.CHANGE_MODAL_STATUS] (line 32) | [types.CHANGE_MODAL_STATUS] (state, {mode, visible}) {
  method getWebsiteConfig (line 43) | getWebsiteConfig ({commit}) {
  method changeModalStatus (line 50) | changeModalStatus ({commit}, payload) {
  method changeDomTitle (line 53) | changeDomTitle ({commit, state}, payload) {

FILE: src/store/modules/contest.js
  method [types.CHANGE_CONTEST] (line 99) | [types.CHANGE_CONTEST] (state, payload) {
  method [types.CHANGE_CONTEST_ITEM_VISIBLE] (line 102) | [types.CHANGE_CONTEST_ITEM_VISIBLE] (state, payload) {
  method [types.CHANGE_RANK_FORCE_UPDATE] (line 105) | [types.CHANGE_RANK_FORCE_UPDATE] (state, payload) {
  method [types.CHANGE_CONTEST_PROBLEMS] (line 108) | [types.CHANGE_CONTEST_PROBLEMS] (state, payload) {
  method [types.CHANGE_CONTEST_RANK_LIMIT] (line 111) | [types.CHANGE_CONTEST_RANK_LIMIT] (state, payload) {
  method [types.CONTEST_ACCESS] (line 114) | [types.CONTEST_ACCESS] (state, payload) {
  method [types.CLEAR_CONTEST] (line 117) | [types.CLEAR_CONTEST] (state) {
  method [types.NOW] (line 128) | [types.NOW] (state, payload) {
  method [types.NOW_ADD_1S] (line 131) | [types.NOW_ADD_1S] (state) {
  method getContest (line 137) | getContest ({commit, rootState, dispatch}) {
  method getContestProblems (line 152) | getContestProblems ({commit, rootState}) {
  method getContestAccess (line 170) | getContestAccess ({commit, rootState}) {

FILE: src/store/modules/user.js
  method [types.CHANGE_PROFILE] (line 30) | [types.CHANGE_PROFILE] (state, {profile}) {
  method getProfile (line 40) | getProfile ({commit}) {
  method clearProfile (line 47) | clearProfile ({commit}) {

FILE: src/store/types.js
  function keyMirror (line 1) | function keyMirror (obj) {

FILE: src/utils/constants.js
  constant JUDGE_STATUS (line 1) | const JUDGE_STATUS = {
  constant CONTEST_STATUS (line 73) | const CONTEST_STATUS = {
  constant CONTEST_STATUS_REVERSE (line 79) | const CONTEST_STATUS_REVERSE = {
  constant RULE_TYPE (line 94) | const RULE_TYPE = {
  constant CONTEST_TYPE (line 99) | const CONTEST_TYPE = {
  constant USER_TYPE (line 104) | const USER_TYPE = {
  constant PROBLEM_PERMISSION (line 110) | const PROBLEM_PERMISSION = {
  constant STORAGE_KEY (line 116) | const STORAGE_KEY = {
  function buildProblemCodeKey (line 122) | function buildProblemCodeKey (problemID, contestID = null) {
  constant GOOGLE_ANALYTICS_ID (line 129) | const GOOGLE_ANALYTICS_ID = 'UA-111499601-1'

FILE: src/utils/filters.js
  function fromNow (line 6) | function fromNow (time) {

FILE: src/utils/storage.js
  method set (line 11) | set (key, value) {
  method get (line 20) | get (key) {
  method remove (line 28) | remove (key) {
  method clear (line 34) | clear () {

FILE: src/utils/time.js
  function utcToLocal (line 4) | function utcToLocal (utcDt, format = 'YYYY-M-D  HH:mm:ss') {
  function duration (line 9) | function duration (startTime, endTime) {
  function secondFormat (line 19) | function secondFormat (seconds) {

FILE: src/utils/utils.js
  function submissionMemoryFormat (line 6) | function submissionMemoryFormat (memory) {
  function submissionTimeFormat (line 13) | function submissionTimeFormat (time) {
  function getACRate (line 18) | function getACRate (acCount, totalCount) {
  function filterEmptyValue (line 24) | function filterEmptyValue (object) {
  function breakLongWords (line 35) | function breakLongWords (value, length = 16) {
  function downloadFile (line 47) | function downloadFile (url) {
  function getLanguages (line 81) | function getLanguages () {
Condensed preview — 135 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (463K chars).
[
  {
    "path": ".babelrc",
    "chars": 416,
    "preview": "{\n  \"presets\": [\n    [\"env\", {\n      \"modules\": false,\n      \"targets\": {\n        \"browsers\": [\"> 1%\", \"last 2 versions\""
  },
  {
    "path": ".editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".eslintignore",
    "chars": 23,
    "preview": "build/*.js\nconfig/*.js\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 698,
    "preview": "module.exports = {\n  root: true,\n  parser: 'babel-eslint',\n  parserOptions: {\n    sourceType: 'module'\n  },\n  // https:/"
  },
  {
    "path": ".gitignore",
    "chars": 986,
    "preview": "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*."
  },
  {
    "path": ".postcssrc.js",
    "chars": 196,
    "preview": "// https://github.com/michael-ciniawsky/postcss-load-config\n\nmodule.exports = {\n  \"plugins\": {\n    // to edit target bro"
  },
  {
    "path": ".travis.yml",
    "chars": 1337,
    "preview": "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   "
  },
  {
    "path": "LICENSE",
    "chars": 4334,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017-present OnineJudge\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "README.md",
    "chars": 2004,
    "preview": "# OnlineJudge Front End\n[![vue](https://img.shields.io/badge/vue-2.5.13-blue.svg?style=flat-square)](https://github.com/"
  },
  {
    "path": "build/build.js",
    "chars": 1072,
    "preview": "'use strict'\nrequire('./check-versions')()\n\nprocess.env.NODE_ENV = 'production'\n\nconst ora = require('ora')\nconst rm = r"
  },
  {
    "path": "build/check-versions.js",
    "chars": 1285,
    "preview": "'use strict'\nconst chalk = require('chalk')\nconst semver = require('semver')\nconst packageConfig = require('../package.j"
  },
  {
    "path": "build/dev-client.js",
    "chars": 258,
    "preview": "/* eslint-disable */\n'use strict'\nrequire('eventsource-polyfill')\nvar hotClient = require('webpack-hot-middleware/client"
  },
  {
    "path": "build/dev-server.js",
    "chars": 3079,
    "preview": "'use strict'\nrequire('./check-versions')()\n\nconst config = require('../config')\nif (!process.env.NODE_ENV) {\n  process.e"
  },
  {
    "path": "build/utils.js",
    "chars": 2101,
    "preview": "'use strict'\nconst path = require('path')\nconst config = require('../config')\nconst ExtractTextPlugin = require('extract"
  },
  {
    "path": "build/vue-loader.conf.js",
    "chars": 432,
    "preview": "'use strict'\nconst utils = require('./utils')\nconst config = require('../config')\nconst isProduction = process.env.NODE_"
  },
  {
    "path": "build/webpack.base.conf.js",
    "chars": 3134,
    "preview": "'use strict'\nconst path = require('path')\nconst glob = require('glob')\nconst webpack = require('webpack')\nconst utils = "
  },
  {
    "path": "build/webpack.dev.conf.js",
    "chars": 1459,
    "preview": "'use strict'\nconst utils = require('./utils')\nconst webpack = require('webpack')\nconst config = require('../config')\ncon"
  },
  {
    "path": "build/webpack.dll.conf.js",
    "chars": 1455,
    "preview": "const webpack = require('webpack');\nconst path = require('path');\nconst UglifyJSPlugin = require('uglifyjs-webpack-plugi"
  },
  {
    "path": "build/webpack.prod.conf.js",
    "chars": 4470,
    "preview": "'use strict'\nconst os = require('os');\nconst path = require('path')\nconst utils = require('./utils')\nconst webpack = req"
  },
  {
    "path": "config/dev.env.js",
    "chars": 313,
    "preview": "let date = require('moment')().format('YYYYMMDD')\nlet commit = require('child_process').execSync('git rev-parse HEAD').t"
  },
  {
    "path": "config/index.js",
    "chars": 2000,
    "preview": "'use strict'\n// Template version: 1.1.1\n// see http://vuejs-templates.github.io/webpack for documentation.\n\nconst path ="
  },
  {
    "path": "config/prod.env.js",
    "chars": 140,
    "preview": "const merge = require('webpack-merge')\nconst devEnv = require('./dev.env')\n\nmodule.exports = merge(devEnv, {\n  NODE_ENV:"
  },
  {
    "path": "deploy/Dockerfile",
    "chars": 180,
    "preview": "FROM node:6.11-alpine\n\nRUN apk add --no-cache nginx git python build-base\n\nVOLUME [ \"/OJ_FE\", \"/var/log/nginx/\", \"/data/"
  },
  {
    "path": "deploy/nginx.conf",
    "chars": 3292,
    "preview": "user nginx;\n\n# Set number of worker processes automatically based on number of CPU cores.\nworker_processes auto;\n\n# Enab"
  },
  {
    "path": "deploy/run.sh",
    "chars": 383,
    "preview": "#!/bin/sh\n\nbase=/OJ_FE\n\nbuild_vendor_dll()\n{\n  if [ ! -e \"${base}/build/vendor-manifest.json\" ]\n  then\n      npm run bui"
  },
  {
    "path": "deploy/sentry_release.sh",
    "chars": 616,
    "preview": "#!/bin/bash\n\nDATE=`date +%Y%m%d`\nCOMMIT=`git rev-parse HEAD`\nVERSION=\"$DATE-${COMMIT:0:5}\"\n\necho \"Current version is $VE"
  },
  {
    "path": "package.json",
    "chars": 3020,
    "preview": "{\n  \"name\": \"onlinejudge\",\n  \"version\": \"2.7.6\",\n  \"description\": \"onlinejudge front end\",\n  \"author\": \"zemal <rawidn@16"
  },
  {
    "path": "src/i18n/admin/en-US.js",
    "chars": 3507,
    "preview": "export const m = {\n  // SideMenu.vue\n  Dashboard: 'Dashboard',\n  General: 'General',\n  User: 'User',\n  Announcement: 'An"
  },
  {
    "path": "src/i18n/admin/zh-CN.js",
    "chars": 2865,
    "preview": "export const m = {\n  // SideMenu.vue\n  Dashboard: '仪表盘',\n  General: '常用设置',\n  User: '用户管理',\n  Announcement: '公告管理',\n  Sy"
  },
  {
    "path": "src/i18n/admin/zh-TW.js",
    "chars": 2632,
    "preview": "export const m = {\n  // SideMenu.vue\n  Dashboard: '儀表板',\n  General: '基本設定',\n  User: '使用者管理',\n  Announcement: '公告管理',\n  S"
  },
  {
    "path": "src/i18n/index.js",
    "chars": 992,
    "preview": "import Vue from 'vue'\nimport VueI18n from 'vue-i18n'\n// ivew UI\nimport ivenUS from 'iview/dist/locale/en-US'\nimport ivzh"
  },
  {
    "path": "src/i18n/oj/en-US.js",
    "chars": 9865,
    "preview": "export const m = {\n  // 404.vue\n  Go_Home: 'Go Home',\n  // Problem.vue\n  Description: 'Description',\n  Input: 'Input',\n "
  },
  {
    "path": "src/i18n/oj/zh-CN.js",
    "chars": 7170,
    "preview": "export const m = {\n    // 404.vue\n  Go_Home: '返回主页',\n  // Problem.vue\n  Description: '题目描述',\n  Input: '输入',\n  Output: '输"
  },
  {
    "path": "src/i18n/oj/zh-TW.js",
    "chars": 7278,
    "preview": "export const m = {\n  // 404.vue\n  Go_Home: '回到首頁',\n  // Problem.vue\n  Description: '題目描述',\n  Input: '輸入',\n  Output: '輸出'"
  },
  {
    "path": "src/pages/admin/App.vue",
    "chars": 529,
    "preview": "<template>\n  <div id=\"app\">\n    <router-view></router-view>\n  </div>\n</template>\n\n<script>\n  export default {\n    name: "
  },
  {
    "path": "src/pages/admin/api.js",
    "chars": 6945,
    "preview": "import Vue from 'vue'\nimport router from './router'\nimport axios from 'axios'\nimport utils from '@/utils/utils'\n\nVue.pro"
  },
  {
    "path": "src/pages/admin/components/Accordion.vue",
    "chars": 1430,
    "preview": "<template>\n  <div class=\"accordion\">\n    <header>\n      <h2>{{title}}</h2>\n      <div class=\"header_right\">\n        <slo"
  },
  {
    "path": "src/pages/admin/components/CodeMirror.vue",
    "chars": 1603,
    "preview": "<template>\n  <codemirror v-model=\"currentValue\" :options=\"options\" ref=\"editor\"></codemirror>\n</template>\n<script>\n  imp"
  },
  {
    "path": "src/pages/admin/components/KatexEditor.vue",
    "chars": 1000,
    "preview": "<template>\n  <el-form>\n    <el-form-item :label=\"$t('m.Input')\">\n      <el-input type=\"textarea\" v-model=\"input\" @change"
  },
  {
    "path": "src/pages/admin/components/Panel.vue",
    "chars": 1880,
    "preview": "<template>\n  <div class=\"panel\" :class=\"{'small': small}\">\n    <header>\n      <div class=\"title\">\n        <template v-if"
  },
  {
    "path": "src/pages/admin/components/ScreenFull.vue",
    "chars": 2649,
    "preview": "<template>\n  <svg @click='click' class=\"icon screenfull\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.o"
  },
  {
    "path": "src/pages/admin/components/SideMenu.vue",
    "chars": 2363,
    "preview": "<template>\n  <el-menu class=\"vertical_menu\"\n           :router=\"true\" :default-active=\"currentPath\">\n    <div class=\"log"
  },
  {
    "path": "src/pages/admin/components/Simditor.vue",
    "chars": 1898,
    "preview": "<template>\n  <textarea ref=\"editor\"></textarea>\n</template>\n\n<script>\n  import Simditor from 'tar-simditor'\n  import 'ta"
  },
  {
    "path": "src/pages/admin/components/TopNav.vue",
    "chars": 446,
    "preview": "<template>\n  <div class=\"breadcrumb\">\n    <el-breadcrumb separator=\">\">\n      <el-breadcrumb-item :to=\"{ path: '/' }\">Ho"
  },
  {
    "path": "src/pages/admin/components/btn/Cancel.vue",
    "chars": 148,
    "preview": "<template>\n  <el-button plain type=\"primary\">{{$t('m.Cancel')}}</el-button>\n</template>\n<script>\n  export default{\n    n"
  },
  {
    "path": "src/pages/admin/components/btn/IconBtn.vue",
    "chars": 467,
    "preview": "<template>\n  <div style=\"display: inline-block;\">\n    <el-tooltip class=\"item\" effect=\"dark\" :content=\"name\" placement=\""
  },
  {
    "path": "src/pages/admin/components/btn/Save.vue",
    "chars": 138,
    "preview": "<template>\n  <el-button type=\"primary\">{{$t('m.Save')}}</el-button>\n</template>\n<script>\n  export default{\n    name: 'Sa"
  },
  {
    "path": "src/pages/admin/components/infoCard.vue",
    "chars": 1715,
    "preview": "<template>\n  <el-card :body-style=\"{padding: 0, height: '100%'}\" class=\"info-card\">\n    <el-row type=\"flex\" class=\"info-"
  },
  {
    "path": "src/pages/admin/components/simditor-file-upload.js",
    "chars": 2160,
    "preview": "/* eslint-disable */\n\nimport Simditor from 'tar-simditor'\nimport * as $ from 'jquery'\n\nvar UploadFile,\n  __hasProp = {}."
  },
  {
    "path": "src/pages/admin/index.html",
    "chars": 435,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <title>OnlineJudge</title>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" co"
  },
  {
    "path": "src/pages/admin/index.js",
    "chars": 1482,
    "preview": "import 'babel-polyfill'\nimport Vue from 'vue'\nimport App from './App.vue'\nimport store from '@/store'\nimport i18n from '"
  },
  {
    "path": "src/pages/admin/router.js",
    "chars": 2695,
    "preview": "import Vue from 'vue'\nimport VueRouter from 'vue-router'\n// 引入 view 组件\nimport { Announcement, Conf, Contest, ContestList"
  },
  {
    "path": "src/pages/admin/style.less",
    "chars": 376,
    "preview": "[class^=\"el-icon-fa\"], [class*=\" el-icon-fa\"] {\n  font-family: FontAwesome !important;\n  font-style: normal;\n  font-weig"
  },
  {
    "path": "src/pages/admin/views/Home.vue",
    "chars": 3111,
    "preview": "<template>\n  <div class=\"container\">\n    <div>\n      <SideMenu></SideMenu>\n    </div>\n    <div id=\"header\">\n      <i cla"
  },
  {
    "path": "src/pages/admin/views/contest/Contest.vue",
    "chars": 5410,
    "preview": "<template>\n  <div class=\"view\">\n    <Panel :title=\"title\">\n      <el-form label-position=\"top\">\n        <el-row :gutter="
  },
  {
    "path": "src/pages/admin/views/contest/ContestList.vue",
    "chars": 5711,
    "preview": "<template>\n  <div class=\"view\">\n    <Panel title=\"Contest List\">\n      <div slot=\"header\">\n        <el-input\n          v"
  },
  {
    "path": "src/pages/admin/views/general/Announcement.vue",
    "chars": 8598,
    "preview": "<template>\n  <div class=\"announcement view\">\n    <Panel :title=\"$t('m.General_Announcement')\">\n      <div class=\"list\">\n"
  },
  {
    "path": "src/pages/admin/views/general/Conf.vue",
    "chars": 5555,
    "preview": "<template>\n  <div class=\"view\">\n    <Panel :title=\"$t('m.SMTP_Config')\">\n      <el-form label-position=\"left\" label-widt"
  },
  {
    "path": "src/pages/admin/views/general/Dashboard.vue",
    "chars": 6651,
    "preview": "<template>\n  <el-row type=\"flex\" :gutter=\"20\">\n    <el-col :md=\"10\" :lg=\"8\">\n      <el-card class=\"admin-info\">\n        "
  },
  {
    "path": "src/pages/admin/views/general/JudgeServer.vue",
    "chars": 3817,
    "preview": "<template>\n  <div class=\"view\">\n    <Panel :title=\"$t('m.Judge_Server_Token')\">\n      <code>{{ token }}</code>\n    </Pan"
  },
  {
    "path": "src/pages/admin/views/general/Login.vue",
    "chars": 2393,
    "preview": "<template>\n  <el-form :model=\"ruleForm2\" :rules=\"rules2\" ref=\"ruleForm2\" label-position=\"left\" label-width=\"0px\"\n       "
  },
  {
    "path": "src/pages/admin/views/general/PruneTestCase.vue",
    "chars": 2077,
    "preview": "<template>\n  <div>\n    <panel>\n      <span slot=\"title\">{{$t('m.Test_Case_Prune_Test_Case')}}\n        <el-popover placem"
  },
  {
    "path": "src/pages/admin/views/general/User.vue",
    "chars": 15210,
    "preview": "<template>\n  <div class=\"view\">\n    <Panel :title=\"$t('m.User_User') \">\n      <div slot=\"header\">\n        <el-row :gutte"
  },
  {
    "path": "src/pages/admin/views/index.js",
    "chars": 774,
    "preview": "import Dashboard from './general/Dashboard.vue'\nimport Announcement from './general/Announcement.vue'\nimport User from '"
  },
  {
    "path": "src/pages/admin/views/problem/AddPublicProblem.vue",
    "chars": 2635,
    "preview": "<template>\n  <div>\n    <el-input\n      v-model=\"keyword\"\n      placeholder=\"Keywords\"\n      prefix-icon=\"el-icon-search\""
  },
  {
    "path": "src/pages/admin/views/problem/ImportAndExport.vue",
    "chars": 4924,
    "preview": "<template>\n  <div>\n    <div style=\"padding-bottom: 10px;\">\n    </div>\n    <panel title=\"Export Problems (beta)\">\n      <"
  },
  {
    "path": "src/pages/admin/views/problem/Problem.vue",
    "chars": 23061,
    "preview": "<template>\n  <div class=\"problem\">\n\n    <Panel :title=\"title\">\n      <el-form ref=\"form\" :model=\"problem\" :rules=\"rules\""
  },
  {
    "path": "src/pages/admin/views/problem/ProblemList.vue",
    "chars": 8620,
    "preview": "<template>\n  <div class=\"view\">\n    <Panel :title=\"contestId ? this.$i18n.t('m.Contest_Problem_List') : this.$i18n.t('m."
  },
  {
    "path": "src/pages/oj/App.vue",
    "chars": 1880,
    "preview": "<template>\n  <div>\n    <NavBar></NavBar>\n    <div class=\"content-app\">\n      <transition name=\"fadeInUp\" mode=\"out-in\">\n"
  },
  {
    "path": "src/pages/oj/api.js",
    "chars": 6538,
    "preview": "import Vue from 'vue'\nimport store from '@/store'\nimport axios from 'axios'\n\nVue.prototype.$http = axios\naxios.defaults."
  },
  {
    "path": "src/pages/oj/components/CodeMirror.vue",
    "chars": 5168,
    "preview": "<template>\n  <div style=\"margin: 0px 0px 15px 0px\">\n    <Row type=\"flex\" justify=\"space-between\" class=\"header\">\n      <"
  },
  {
    "path": "src/pages/oj/components/Highlight.vue",
    "chars": 785,
    "preview": "<template>\n  <pre v-highlight=\"code\"><code :class=\"language\" :style=\"styleObject\"></code></pre>\n</template>\n\n<script>\n  "
  },
  {
    "path": "src/pages/oj/components/NavBar.vue",
    "chars": 4883,
    "preview": "<template>\n  <div id=\"header\">\n    <Menu theme=\"light\" mode=\"horizontal\" @on-select=\"handleRoute\" :active-name=\"activeMe"
  },
  {
    "path": "src/pages/oj/components/Pagination.vue",
    "chars": 1215,
    "preview": "<template>\n  <div class=\"page\">\n    <Page :total=\"total\"\n          :page-size=\"pageSize\"\n          @on-change=\"onChange\""
  },
  {
    "path": "src/pages/oj/components/Panel.vue",
    "chars": 1237,
    "preview": "<template>\n  <Card :padding=\"padding\" :shadow=\"shadow\" :dis-hover=\"disHover\" :bordered=\"bordered\">\n    <div slot=\"title\""
  },
  {
    "path": "src/pages/oj/components/mixins/emitter.js",
    "chars": 928,
    "preview": "function broadcast (componentName, eventName, params) {\n  this.$children.forEach(child => {\n    const name = child.$opti"
  },
  {
    "path": "src/pages/oj/components/mixins/form.js",
    "chars": 531,
    "preview": "import api from '@oj/api'\n\nexport default {\n  data () {\n    return {\n      captchaSrc: ''\n    }\n  },\n  methods: {\n    va"
  },
  {
    "path": "src/pages/oj/components/mixins/index.js",
    "chars": 143,
    "preview": "import Emitter from './emitter'\nimport ProblemMixin from './problem'\nimport FormMixin from './form'\n\nexport {Emitter, Pr"
  },
  {
    "path": "src/pages/oj/components/mixins/problem.js",
    "chars": 1144,
    "preview": "import utils from '@/utils/utils'\n\nexport default {\n  data () {\n    return {\n      statusColumn: false\n    }\n  },\n  meth"
  },
  {
    "path": "src/pages/oj/components/verticalMenu/verticalMenu-item.vue",
    "chars": 1184,
    "preview": "<template>\n  <li @click.stop=\"handleClick\" :class=\"{disabled: disabled}\">\n    <slot></slot>\n  </li>\n</template>\n\n<script"
  },
  {
    "path": "src/pages/oj/components/verticalMenu/verticalMenu.vue",
    "chars": 209,
    "preview": "<template>\n  <Card :padding=\"0\" dis-hover>\n    <ul>\n      <slot></slot>\n    </ul>\n  </Card>\n</template>\n\n<script>\n  expo"
  },
  {
    "path": "src/pages/oj/index.html",
    "chars": 1048,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>OnlineJudge</title>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"wid"
  },
  {
    "path": "src/pages/oj/index.js",
    "chars": 1949,
    "preview": "import 'babel-polyfill'\nimport Vue from 'vue'\nimport App from './App.vue'\nimport router from './router'\nimport store fro"
  },
  {
    "path": "src/pages/oj/router/index.js",
    "chars": 1035,
    "preview": "import Vue from 'vue'\nimport VueRouter from 'vue-router'\nimport routes from './routes'\nimport storage from '@/utils/stor"
  },
  {
    "path": "src/pages/oj/router/routes.js",
    "chars": 3785,
    "preview": "// all routes here.\nimport {\n  About,\n  ACMRank,\n  Announcements,\n  ApplyResetPassword,\n  FAQ,\n  Home,\n  Logout,\n  NotFo"
  },
  {
    "path": "src/pages/oj/views/contest/ContestDetail.vue",
    "chars": 7211,
    "preview": "<template>\n  <div class=\"flex-container\">\n    <div id=\"contest-main\">\n      <!--children-->\n      <transition name=\"fade"
  },
  {
    "path": "src/pages/oj/views/contest/ContestList.vue",
    "chars": 7372,
    "preview": "<template>\n  <Row type=\"flex\">\n    <Col :span=\"24\">\n    <Panel id=\"contest-card\" shadow>\n      <div slot=\"title\">{{query"
  },
  {
    "path": "src/pages/oj/views/contest/children/ACMContestRank.vue",
    "chars": 10372,
    "preview": "<template>\n  <Panel shadow>\n    <div slot=\"title\">{{ contest.title }}</div>\n    <div slot=\"extra\">\n      <screen-full :h"
  },
  {
    "path": "src/pages/oj/views/contest/children/ACMHelper.vue",
    "chars": 6668,
    "preview": "<template>\n  <panel shadow>\n    <div slot=\"title\">{{$t('m.ACM_Helper')}}</div>\n    <div slot=\"extra\">\n      <ul class=\"f"
  },
  {
    "path": "src/pages/oj/views/contest/children/ContestProblemList.vue",
    "chars": 2606,
    "preview": "<template>\n  <div>\n    <Panel>\n      <div slot=\"title\">{{$t('m.Problems_List')}}</div>\n      <Table v-if=\"contestRuleTyp"
  },
  {
    "path": "src/pages/oj/views/contest/children/ContestRank.vue",
    "chars": 952,
    "preview": "<template>\n  <div>\n    <component :is=\"currentView\"></component>\n  </div>\n</template>\n\n<script>\n  import { mapGetters } "
  },
  {
    "path": "src/pages/oj/views/contest/children/OIContestRank.vue",
    "chars": 7417,
    "preview": "<template>\n  <Panel shadow>\n    <div slot=\"title\">{{ contest.title }}</div>\n    <div slot=\"extra\">\n      <screen-full :h"
  },
  {
    "path": "src/pages/oj/views/contest/children/contestRankMixin.js",
    "chars": 3181,
    "preview": "import api from '@oj/api'\nimport ScreenFull from '@admin/components/ScreenFull.vue'\nimport { mapGetters, mapState } from"
  },
  {
    "path": "src/pages/oj/views/contest/index.js",
    "chars": 578,
    "preview": "const ContestList = () => import(/* webpackChunkName: \"contest\" */ './ContestList.vue')\nconst ContestDetails = () => imp"
  },
  {
    "path": "src/pages/oj/views/general/404.vue",
    "chars": 1992,
    "preview": "<template>\n  <div class=\"error404\">\n    <div class=\"error404-body-con\">\n      <Card>\n        <div class=\"error404-body-c"
  },
  {
    "path": "src/pages/oj/views/general/Announcements.vue",
    "chars": 4463,
    "preview": "<template>\n  <Panel shadow :padding=\"10\">\n    <div slot=\"title\">\n      {{title}}\n    </div>\n    <div slot=\"extra\">\n     "
  },
  {
    "path": "src/pages/oj/views/general/Home.vue",
    "chars": 2453,
    "preview": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"22\">\n    <panel shadow v-if=\"contests.length\" clas"
  },
  {
    "path": "src/pages/oj/views/help/About.vue",
    "chars": 2058,
    "preview": "<template>\n  <div>\n    <panel class=\"container\">\n      <div slot=\"title\">{{$t('m.Compiler')}} & {{$t('m.Judger')}}</div>"
  },
  {
    "path": "src/pages/oj/views/help/FAQ.vue",
    "chars": 2628,
    "preview": "<template>\n  <panel>\n    <div slot=\"title\">{{$t('m.Frequently_Asked_Questions')}}</div>\n    <div class=\"content markdown"
  },
  {
    "path": "src/pages/oj/views/index.js",
    "chars": 1459,
    "preview": "import ProblemList from './problem/ProblemList.vue'\nimport Logout from './user/Logout.vue'\nimport UserHome from './user/"
  },
  {
    "path": "src/pages/oj/views/problem/Problem.vue",
    "chars": 19743,
    "preview": "<template>\n  <div class=\"flex-container\">\n    <div id=\"problem-main\">\n      <!--problem main-->\n      <Panel :padding=\"4"
  },
  {
    "path": "src/pages/oj/views/problem/ProblemList.vue",
    "chars": 8775,
    "preview": "<template>\n  <Row type=\"flex\" :gutter=\"18\">\n    <Col :span=19>\n    <Panel shadow>\n      <div slot=\"title\">{{$t('m.Proble"
  },
  {
    "path": "src/pages/oj/views/problem/chartData.js",
    "chars": 2052,
    "preview": "const pieColorMap = {\n  'AC': {color: '#19be6b'},\n  'WA': {color: '#ed3f14'},\n  'TLE': {color: '#ff9300'},\n  'MLE': {col"
  },
  {
    "path": "src/pages/oj/views/rank/ACMRank.vue",
    "chars": 5425,
    "preview": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"22\">\n    <Panel :padding=\"10\">\n      <div slot=\"ti"
  },
  {
    "path": "src/pages/oj/views/rank/OIRank.vue",
    "chars": 5088,
    "preview": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"22\">\n    <Panel :padding=\"10\">\n      <div slot=\"ti"
  },
  {
    "path": "src/pages/oj/views/setting/Settings.vue",
    "chars": 3513,
    "preview": "<template>\n  <div class=\"container\">\n    <Card :padding=\"0\">\n      <div class=\"flex-container\">\n        <div class=\"menu"
  },
  {
    "path": "src/pages/oj/views/setting/children/AccountSetting.vue",
    "chars": 6060,
    "preview": "<template>\n  <div class=\"setting-main\">\n    <div class=\"flex-container\">\n      <div class=\"left\">\n        <p class=\"sect"
  },
  {
    "path": "src/pages/oj/views/setting/children/ProfileSetting.vue",
    "chars": 8415,
    "preview": "<template>\n  <div class=\"setting-main\">\n    <div class=\"section-title\">{{$t('m.Avatar_Setting')}}</div>\n    <template v-"
  },
  {
    "path": "src/pages/oj/views/setting/children/SecuritySetting.vue",
    "chars": 5914,
    "preview": "<template>\n  <div class=\"setting-main\">\n    <p class=\"section-title\">{{$t('m.Sessions')}}</p>\n    <div class=\"flex-conta"
  },
  {
    "path": "src/pages/oj/views/setting/index.js",
    "chars": 461,
    "preview": "const Settings = () => import(/* webpackChunkName: \"setting\" */ './Settings.vue')\nconst ProfileSetting = () => import(/*"
  },
  {
    "path": "src/pages/oj/views/submission/SubmissionDetails.vue",
    "chars": 5930,
    "preview": "<template>\n  <Row type=\"flex\" justify=\"space-around\">\n    <Col :span=\"20\" id=\"status\">\n      <Alert :type=\"status.type\" "
  },
  {
    "path": "src/pages/oj/views/submission/SubmissionList.vue",
    "chars": 10632,
    "preview": "<template>\n  <div class=\"flex-container\">\n    <div id=\"main\">\n      <Panel shadow>\n        <div slot=\"title\">{{title}}</"
  },
  {
    "path": "src/pages/oj/views/user/ApplyResetPassword.vue",
    "chars": 3361,
    "preview": "<template>\n  <Panel :padding=\"30\" class=\"container\">\n    <div slot=\"title\" class=\"center\">{{$t('m.Reset_Password')}}</di"
  },
  {
    "path": "src/pages/oj/views/user/Login.vue",
    "chars": 3611,
    "preview": "<template>\n  <div>\n    <Form ref=\"formLogin\" :model=\"formLogin\" :rules=\"ruleLogin\">\n      <FormItem prop=\"username\">\n   "
  },
  {
    "path": "src/pages/oj/views/user/Logout.vue",
    "chars": 272,
    "preview": "<template>\n</template>\n\n<script>\n  import api from '../../api.js'\n\n  export default {\n    mounted () {\n      api.logout("
  },
  {
    "path": "src/pages/oj/views/user/Register.vue",
    "chars": 5607,
    "preview": "<template>\n<div>\n    <Form ref=\"formRegister\" :model=\"formRegister\" :rules=\"ruleRegister\">\n      <FormItem prop=\"usernam"
  },
  {
    "path": "src/pages/oj/views/user/ResetPassword.vue",
    "chars": 4114,
    "preview": "<template>\n  <Panel :padding=\"30\" class=\"container\">\n    <div slot=\"title\" class=\"center\">{{$t('m.Reset_Password')}}</di"
  },
  {
    "path": "src/pages/oj/views/user/UserHome.vue",
    "chars": 5610,
    "preview": "<template>\n  <div class=\"container\">\n    <div class=\"avatar-container\">\n      <img class=\"avatar\" :src=\"profile.avatar\"/"
  },
  {
    "path": "src/plugins/highlight.js",
    "chars": 1222,
    "preview": "import hljs from 'highlight.js/lib/highlight'\nimport cpp from 'highlight.js/lib/languages/cpp'\nimport python from 'highl"
  },
  {
    "path": "src/plugins/katex.js",
    "chars": 765,
    "preview": "import 'katex'\nimport renderMathInElement from 'katex/contrib/auto-render/auto-render'\nimport 'katex/dist/katex.min.css'"
  },
  {
    "path": "src/store/index.js",
    "chars": 1619,
    "preview": "import Vue from 'vue'\nimport Vuex from 'vuex'\nimport user from './modules/user'\nimport contest from './modules/contest'\n"
  },
  {
    "path": "src/store/modules/contest.js",
    "chars": 5606,
    "preview": "import moment from 'moment'\nimport types from '../types'\nimport api from '@oj/api'\nimport { CONTEST_STATUS, USER_TYPE, C"
  },
  {
    "path": "src/store/modules/user.js",
    "chars": 1363,
    "preview": "import types from '../types'\nimport api from '@oj/api'\nimport storage from '@/utils/storage'\nimport i18n from '@/i18n'\ni"
  },
  {
    "path": "src/store/types.js",
    "chars": 595,
    "preview": "function keyMirror (obj) {\n  if (obj instanceof Object) {\n    var _obj = Object.assign({}, obj)\n    var _keyArray = Obje"
  },
  {
    "path": "src/styles/common.less",
    "chars": 1413,
    "preview": "html {\n  background-color: #eee;\n}\n\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: \"Helvetica Neue\", Helvetica, \"PingF"
  },
  {
    "path": "src/styles/index.less",
    "chars": 56,
    "preview": "@import './common.less';\n@import './iview-custom.less';\n"
  },
  {
    "path": "src/styles/iview-custom.less",
    "chars": 609,
    "preview": "table {\n  width: 100% !important;\n}\n.auto-resize {\n  table {\n    table-layout: auto !important;\n  }\n}\n.ivu-table-wrapper"
  },
  {
    "path": "src/styles/markdown.less",
    "chars": 1516,
    "preview": "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}"
  },
  {
    "path": "src/utils/constants.js",
    "chars": 2140,
    "preview": "export const JUDGE_STATUS = {\n  '-2': {\n    name: 'Compile Error',\n    short: 'CE',\n    color: 'yellow',\n    type: 'warn"
  },
  {
    "path": "src/utils/filters.js",
    "chars": 322,
    "preview": "import moment from 'moment'\nimport utils from './utils'\nimport time from './time'\n\n// 友好显示时间\nfunction fromNow (time) {\n "
  },
  {
    "path": "src/utils/sentry.js",
    "chars": 562,
    "preview": "import Vue from 'vue'\nimport Raven from 'raven-js'\nimport RavenVue from 'raven-js/plugins/vue'\n\nconst options = {\n  rele"
  },
  {
    "path": "src/utils/storage.js",
    "chars": 625,
    "preview": "const localStorage = window.localStorage\n\nexport default {\n  name: 'storage',\n\n  /**\n   * save value(Object) to key\n   *"
  },
  {
    "path": "src/utils/time.js",
    "chars": 814,
    "preview": "import moment from 'moment'\n\n// convert utc time to localtime\nfunction utcToLocal (utcDt, format = 'YYYY-M-D  HH:mm:ss')"
  },
  {
    "path": "src/utils/utils.js",
    "chars": 2970,
    "preview": "import Vue from 'vue'\nimport storage from '@/utils/storage'\nimport { STORAGE_KEY } from '@/utils/constants'\nimport ojAPI"
  },
  {
    "path": "static/css/loader.css",
    "chars": 2144,
    "preview": "@-webkit-keyframes enter {\n  0% {\n    opacity: 0;\n    top: -10px;\n  }\n  5% {\n    opacity: 1;\n    top: 0px;\n  }\n  50.9% {"
  }
]

About this extraction

This page contains the full source code of the QingdaoU/OnlineJudgeFE GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 135 files (416.1 KB), approximately 113.8k tokens, and a symbol index with 178 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!