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 ", "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 ================================================ ================================================ 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 ================================================ ================================================ FILE: src/pages/admin/components/CodeMirror.vue ================================================ ================================================ FILE: src/pages/admin/components/KatexEditor.vue ================================================ ================================================ FILE: src/pages/admin/components/Panel.vue ================================================ ================================================ FILE: src/pages/admin/components/ScreenFull.vue ================================================ ================================================ FILE: src/pages/admin/components/SideMenu.vue ================================================ ================================================ FILE: src/pages/admin/components/Simditor.vue ================================================ ================================================ FILE: src/pages/admin/components/TopNav.vue ================================================ ================================================ FILE: src/pages/admin/components/btn/Cancel.vue ================================================ ================================================ FILE: src/pages/admin/components/btn/IconBtn.vue ================================================ ================================================ FILE: src/pages/admin/components/btn/Save.vue ================================================ ================================================ FILE: src/pages/admin/components/infoCard.vue ================================================ ================================================ 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 = $('', { 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 = '' + res.file_name + '' _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 ================================================ OnlineJudge
================================================ 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 ================================================ ================================================ FILE: src/pages/admin/views/contest/Contest.vue ================================================ ================================================ FILE: src/pages/admin/views/contest/ContestList.vue ================================================ ================================================ FILE: src/pages/admin/views/general/Announcement.vue ================================================ ================================================ FILE: src/pages/admin/views/general/Conf.vue ================================================ ================================================ FILE: src/pages/admin/views/general/Dashboard.vue ================================================ ================================================ FILE: src/pages/admin/views/general/JudgeServer.vue ================================================ ================================================ FILE: src/pages/admin/views/general/Login.vue ================================================ ================================================ FILE: src/pages/admin/views/general/PruneTestCase.vue ================================================ ================================================ FILE: src/pages/admin/views/general/User.vue ================================================ ================================================ 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 ================================================ ================================================ FILE: src/pages/admin/views/problem/ImportAndExport.vue ================================================ ================================================ FILE: src/pages/admin/views/problem/Problem.vue ================================================ ================================================ FILE: src/pages/admin/views/problem/ProblemList.vue ================================================ ================================================ FILE: src/pages/oj/App.vue ================================================ ================================================ FILE: src/pages/oj/api.js ================================================ import Vue from 'vue' import store from '@/store' import axios from 'axios' Vue.prototype.$http = axios axios.defaults.baseURL = '/api' axios.defaults.xsrfHeaderName = 'X-CSRFToken' axios.defaults.xsrfCookieName = 'csrftoken' export default { getWebsiteConf (params) { return ajax('website', 'get', { params }) }, getAnnouncementList (offset, limit) { let params = { offset: offset, limit: limit } return ajax('announcement', 'get', { params }) }, login (data) { return ajax('login', 'post', { data }) }, checkUsernameOrEmail (username, email) { return ajax('check_username_or_email', 'post', { data: { username, email } }) }, // 注册 register (data) { return ajax('register', 'post', { data }) }, logout () { return ajax('logout', 'get') }, getCaptcha () { return ajax('captcha', 'get') }, getUserInfo (username = undefined) { return ajax('profile', 'get', { params: { username } }) }, updateProfile (profile) { return ajax('profile', 'put', { data: profile }) }, freshDisplayID (userID) { return ajax('profile/fresh_display_id', 'get', { params: { user_id: userID } }) }, twoFactorAuth (method, data) { return ajax('two_factor_auth', method, { data }) }, tfaRequiredCheck (username) { return ajax('tfa_required', 'post', { data: { username } }) }, getSessions () { return ajax('sessions', 'get') }, deleteSession (sessionKey) { return ajax('sessions', 'delete', { params: { session_key: sessionKey } }) }, applyResetPassword (data) { return ajax('apply_reset_password', 'post', { data }) }, resetPassword (data) { return ajax('reset_password', 'post', { data }) }, changePassword (data) { return ajax('change_password', 'post', { data }) }, changeEmail (data) { return ajax('change_email', 'post', { data }) }, getLanguages () { return ajax('languages', 'get') }, getProblemTagList () { return ajax('problem/tags', 'get') }, getProblemList (offset, limit, searchParams) { let params = { paging: true, offset, limit } Object.keys(searchParams).forEach((element) => { if (searchParams[element]) { params[element] = searchParams[element] } }) return ajax('problem', 'get', { params: params }) }, pickone () { return ajax('pickone', 'get') }, getProblem (problemID) { return ajax('problem', 'get', { params: { problem_id: problemID } }) }, getContestList (offset, limit, searchParams) { let params = { offset, limit } if (searchParams !== undefined) { Object.keys(searchParams).forEach((element) => { if (searchParams[element]) { params[element] = searchParams[element] } }) } return ajax('contests', 'get', { params }) }, getContest (id) { return ajax('contest', 'get', { params: { id } }) }, getContestAccess (contestID) { return ajax('contest/access', 'get', { params: { contest_id: contestID } }) }, checkContestPassword (contestID, password) { return ajax('contest/password', 'post', { data: { contest_id: contestID, password } }) }, getContestAnnouncementList (contestId) { return ajax('contest/announcement', 'get', { params: { contest_id: contestId } }) }, getContestProblemList (contestId) { return ajax('contest/problem', 'get', { params: { contest_id: contestId } }) }, getContestProblem (problemID, contestID) { return ajax('contest/problem', 'get', { params: { contest_id: contestID, problem_id: problemID } }) }, submitCode (data) { return ajax('submission', 'post', { data }) }, getSubmissionList (offset, limit, params) { params.limit = limit params.offset = offset return ajax('submissions', 'get', { params }) }, getContestSubmissionList (offset, limit, params) { params.limit = limit params.offset = offset return ajax('contest_submissions', 'get', { params }) }, getSubmission (id) { return ajax('submission', 'get', { params: { id } }) }, submissionExists (problemID) { return ajax('submission_exists', 'get', { params: { problem_id: problemID } }) }, submissionRejudge (id) { return ajax('admin/submission/rejudge', 'get', { params: { id } }) }, updateSubmission (data) { return ajax('submission', 'put', { data }) }, getUserRank (offset, limit, rule = 'acm') { let params = { offset, limit, rule } return ajax('user_rank', 'get', { params }) }, getContestRank (params) { return ajax('contest_rank', 'get', { params }) }, getACMACInfo (params) { return ajax('admin/contest/acm_helper', 'get', { params }) }, updateACInfoCheckedStatus (data) { return ajax('admin/contest/acm_helper', 'put', { 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')) { store.dispatch('changeModalStatus', {'mode': 'login', 'visible': true}) } } 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/oj/components/CodeMirror.vue ================================================ ================================================ FILE: src/pages/oj/components/Highlight.vue ================================================ ================================================ FILE: src/pages/oj/components/NavBar.vue ================================================ ================================================ FILE: src/pages/oj/components/Pagination.vue ================================================ ================================================ FILE: src/pages/oj/components/Panel.vue ================================================ ================================================ FILE: src/pages/oj/components/mixins/emitter.js ================================================ function broadcast (componentName, eventName, params) { this.$children.forEach(child => { const name = child.$options.name if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)) } else { // todo 如果 params 是空数组,接收到的会是 undefined broadcast.apply(child, [componentName, eventName].concat([params])) } }) } export default { methods: { dispatch (componentName, eventName, params) { let parent = this.$parent || this.$root let name = parent.$options.name while (parent && (!name || name !== componentName)) { parent = parent.$parent if (parent) { name = parent.$options.name } } if (parent) { parent.$emit.apply(parent, [eventName].concat(params)) } }, broadcast (componentName, eventName, params) { broadcast.call(this, componentName, eventName, params) } } } ================================================ FILE: src/pages/oj/components/mixins/form.js ================================================ import api from '@oj/api' export default { data () { return { captchaSrc: '' } }, methods: { validateForm (formName) { return new Promise((resolve, reject) => { this.$refs[formName].validate(valid => { if (!valid) { this.$error('please validate the error fields') } else { resolve(valid) } }) }) }, getCaptchaSrc () { api.getCaptcha().then(res => { this.captchaSrc = res.data.data }) } } } ================================================ FILE: src/pages/oj/components/mixins/index.js ================================================ import Emitter from './emitter' import ProblemMixin from './problem' import FormMixin from './form' export {Emitter, ProblemMixin, FormMixin} ================================================ FILE: src/pages/oj/components/mixins/problem.js ================================================ import utils from '@/utils/utils' export default { data () { return { statusColumn: false } }, methods: { getACRate (ACCount, TotalCount) { return utils.getACRate(ACCount, TotalCount) }, addStatusColumn (tableColumns, dataProblems) { // 已添加过直接返回 if (this.statusColumn) return // 只在有做题记录时才添加column let needAdd = dataProblems.some((item, index) => { if (item.my_status !== null && item.my_status !== undefined) { return true } }) if (!needAdd) { return } tableColumns.splice(0, 0, { width: 60, title: ' ', render: (h, params) => { let status = params.row.my_status if (status === null || status === undefined) { return undefined } return h('Icon', { props: { type: status === 0 ? 'checkmark-round' : 'minus-round', size: '16' }, style: { color: status === 0 ? '#19be6b' : '#ed3f14' } }) } }) this.statusColumn = true } } } ================================================ FILE: src/pages/oj/components/verticalMenu/verticalMenu-item.vue ================================================ ================================================ FILE: src/pages/oj/components/verticalMenu/verticalMenu.vue ================================================ ================================================ FILE: src/pages/oj/index.html ================================================ OnlineJudge
================================================ FILE: src/pages/oj/index.js ================================================ import 'babel-polyfill' import Vue from 'vue' import App from './App.vue' import router from './router' import store from '@/store' import i18n from '@/i18n' import VueClipboard from 'vue-clipboard2' import VueAnalytics from 'vue-analytics' import { GOOGLE_ANALYTICS_ID } from '@/utils/constants' import iView from 'iview' import 'iview/dist/styles/iview.css' import Panel from '@oj/components/Panel.vue' import VerticalMenu from '@oj/components/verticalMenu/verticalMenu.vue' import VerticalMenuItem from '@oj/components/verticalMenu/verticalMenu-item.vue' import '@/styles/index.less' import highlight from '@/plugins/highlight' import katex from '@/plugins/katex' import filters from '@/utils/filters.js' import ECharts from 'vue-echarts/components/ECharts.vue' import 'echarts/lib/chart/bar' import 'echarts/lib/chart/line' import 'echarts/lib/chart/pie' import 'echarts/lib/component/title' import 'echarts/lib/component/grid' import 'echarts/lib/component/dataZoom' import 'echarts/lib/component/legend' import 'echarts/lib/component/tooltip' import 'echarts/lib/component/toolbox' import 'echarts/lib/component/markPoint' // register global utility filters. Object.keys(filters).forEach(key => { Vue.filter(key, filters[key]) }) Vue.config.productionTip = false Vue.use(iView, { i18n: (key, value) => i18n.t(key, value) }) Vue.use(VueClipboard) Vue.use(highlight) Vue.use(katex) Vue.use(VueAnalytics, { id: GOOGLE_ANALYTICS_ID, router }) Vue.component('ECharts', ECharts) Vue.component(VerticalMenu.name, VerticalMenu) Vue.component(VerticalMenuItem.name, VerticalMenuItem) Vue.component(Panel.name, Panel) // 注册全局消息提示 Vue.prototype.$Message.config({ duration: 2 }) Vue.prototype.$error = (s) => Vue.prototype.$Message.error(s) Vue.prototype.$info = (s) => Vue.prototype.$Message.info(s) Vue.prototype.$success = (s) => Vue.prototype.$Message.success(s) new Vue(Vue.util.extend({router, store, i18n}, App)).$mount('#app') ================================================ FILE: src/pages/oj/router/index.js ================================================ import Vue from 'vue' import VueRouter from 'vue-router' import routes from './routes' import storage from '@/utils/storage' import {STORAGE_KEY} from '@/utils/constants' import {sync} from 'vuex-router-sync' import {types, default as store} from '../../../store' Vue.use(VueRouter) const router = new VueRouter({ mode: 'history', scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return {x: 0, y: 0} } }, routes }) // 全局身份确认 router.beforeEach((to, from, next) => { Vue.prototype.$Loading.start() if (to.matched.some(record => record.meta.requiresAuth)) { if (!storage.get(STORAGE_KEY.AUTHED)) { Vue.prototype.$error('Please login first') store.commit(types.CHANGE_MODAL_STATUS, {mode: 'login', visible: true}) next({ name: 'home' }) } else { next() } } else { next() } }) router.afterEach((to, from, next) => { Vue.prototype.$Loading.finish() }) sync(store, router) export default router ================================================ FILE: src/pages/oj/router/routes.js ================================================ // all routes here. import { About, ACMRank, Announcements, ApplyResetPassword, FAQ, Home, Logout, NotFound, OIRank, Problem, ProblemList, ResetPassword, SubmissionDetails, SubmissionList, UserHome } from '../views' import * as Contest from '@oj/views/contest' import * as Setting from '@oj/views/setting' export default [ { name: 'home', path: '/', meta: {title: 'Home'}, component: Home }, { name: 'logout', path: '/logout', meta: {title: 'Logout'}, component: Logout }, { name: 'apply-reset-password', path: '/apply-reset-password', meta: {title: 'Apply Reset Password'}, component: ApplyResetPassword }, { name: 'reset-password', path: '/reset-password/:token', meta: {title: 'Reset Password'}, component: ResetPassword }, { name: 'problem-list', path: '/problem', meta: {title: 'Problem List'}, component: ProblemList }, { name: 'problem-details', path: '/problem/:problemID', meta: {title: 'Problem Details'}, component: Problem }, { name: 'submission-list', path: '/status', meta: {title: 'Submission List'}, component: SubmissionList }, { name: 'submission-details', path: '/status/:id/', meta: {title: 'Submission Details'}, component: SubmissionDetails }, { name: 'contest-list', path: '/contest', meta: {title: 'Contest List'}, component: Contest.ContestList }, { name: 'contest-details', path: '/contest/:contestID/', component: Contest.ContestDetails, meta: {title: 'Contest Details'}, children: [ { name: 'contest-submission-list', path: 'submissions', component: SubmissionList }, { name: 'contest-problem-list', path: 'problems', component: Contest.ContestProblemList }, { name: 'contest-problem-details', path: 'problem/:problemID/', component: Problem }, { name: 'contest-announcement-list', path: 'announcements', component: Announcements }, { name: 'contest-rank', path: 'rank', component: Contest.ContestRank }, { name: 'acm-helper', path: 'helper', component: Contest.ACMContestHelper } ] }, { name: 'acm-rank', path: '/acm-rank', meta: {title: 'ACM Rankings'}, component: ACMRank }, { name: 'oi-rank', path: '/oi-rank', meta: {title: 'OI Rankings'}, component: OIRank }, { name: 'user-home', path: '/user-home', component: UserHome, meta: {requiresAuth: true, title: 'User Home'} }, { path: '/setting', component: Setting.Settings, children: [ { name: 'default-setting', path: '', meta: {requiresAuth: true, title: 'Default Settings'}, component: Setting.ProfileSetting }, { name: 'profile-setting', path: 'profile', meta: {requiresAuth: true, title: 'Profile Settings'}, component: Setting.ProfileSetting }, { name: 'account-setting', path: 'account', meta: {requiresAuth: true, title: 'Account Settings'}, component: Setting.AccountSetting }, { name: 'security-setting', path: 'security', meta: {requiresAuth: true, title: 'Security Settings'}, component: Setting.SecuritySetting } ] }, { path: '/about', name: 'about', meta: {title: 'About'}, component: About }, { path: '/faq', name: 'faq', meta: {title: 'FAQ'}, component: FAQ }, { path: '*', meta: {title: '404'}, component: NotFound } ] ================================================ FILE: src/pages/oj/views/contest/ContestDetail.vue ================================================ ================================================ FILE: src/pages/oj/views/contest/ContestList.vue ================================================ ================================================ FILE: src/pages/oj/views/contest/children/ACMContestRank.vue ================================================ ================================================ FILE: src/pages/oj/views/contest/children/ACMHelper.vue ================================================ ================================================ FILE: src/pages/oj/views/contest/children/ContestProblemList.vue ================================================ ================================================ FILE: src/pages/oj/views/contest/children/ContestRank.vue ================================================ ================================================ FILE: src/pages/oj/views/contest/children/OIContestRank.vue ================================================ ================================================ FILE: src/pages/oj/views/contest/children/contestRankMixin.js ================================================ import api from '@oj/api' import ScreenFull from '@admin/components/ScreenFull.vue' import { mapGetters, mapState } from 'vuex' import { types } from '@/store' import { CONTEST_STATUS } from '@/utils/constants' export default { components: { ScreenFull }, methods: { getContestRankData (page = 1, refresh = false) { let offset = (page - 1) * this.limit if (this.showChart && !refresh) { this.$refs.chart.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'}) } let params = { offset, limit: this.limit, contest_id: this.$route.params.contestID, force_refresh: this.forceUpdate ? '1' : '0' } api.getContestRank(params).then(res => { if (this.showChart && !refresh) { this.$refs.chart.hideLoading() } this.total = res.data.data.total if (page === 1) { this.applyToChart(res.data.data.results.slice(0, 10)) } this.applyToTable(res.data.data.results) }) }, handleAutoRefresh (status) { if (status === true) { this.refreshFunc = setInterval(() => { this.page = 1 this.getContestRankData(1, true) }, 10000) } else { clearInterval(this.refreshFunc) } } }, computed: { ...mapGetters(['isContestAdmin']), ...mapState({ 'contest': state => state.contest.contest, 'contestProblems': state => state.contest.contestProblems }), showChart: { get () { return this.$store.state.contest.itemVisible.chart }, set (value) { this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {chart: value}) } }, showMenu: { get () { return this.$store.state.contest.itemVisible.menu }, set (value) { this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {menu: value}) this.$nextTick(() => { if (this.showChart) { this.$refs.chart.resize() } this.$refs.tableRank.handleResize() }) } }, showRealName: { get () { return this.$store.state.contest.itemVisible.realName }, set (value) { this.$store.commit(types.CHANGE_CONTEST_ITEM_VISIBLE, {realName: value}) if (value) { this.columns.splice(2, 0, { title: 'RealName', align: 'center', width: 150, render: (h, {row}) => { return h('span', row.user.real_name) } }) } else { this.columns.splice(2, 1) } } }, forceUpdate: { get () { return this.$store.state.contest.forceUpdate }, set (value) { this.$store.commit(types.CHANGE_RANK_FORCE_UPDATE, {value: value}) } }, limit: { get () { return this.$store.state.contest.rankLimit }, set (value) { this.$store.commit(types.CHANGE_CONTEST_RANK_LIMIT, {rankLimit: value}) } }, refreshDisabled () { return this.contest.status === CONTEST_STATUS.ENDED } }, beforeDestroy () { clearInterval(this.refreshFunc) } } ================================================ FILE: src/pages/oj/views/contest/index.js ================================================ const ContestList = () => import(/* webpackChunkName: "contest" */ './ContestList.vue') const ContestDetails = () => import(/* webpackChunkName: "contest" */ './ContestDetail.vue') const ContestProblemList = () => import(/* webpackChunkName: "contest" */ './children/ContestProblemList.vue') const ContestRank = () => import(/* webpackChunkName: "contest" */ './children/ContestRank.vue') const ACMContestHelper = () => import(/* webpackChunkName: "contest" */ './children/ACMHelper.vue') export {ContestDetails, ContestList, ContestProblemList, ContestRank, ACMContestHelper} ================================================ FILE: src/pages/oj/views/general/404.vue ================================================ ================================================ FILE: src/pages/oj/views/general/Announcements.vue ================================================ ================================================ FILE: src/pages/oj/views/general/Home.vue ================================================ ================================================ FILE: src/pages/oj/views/help/About.vue ================================================ ================================================ FILE: src/pages/oj/views/help/FAQ.vue ================================================ ================================================ FILE: src/pages/oj/views/index.js ================================================ import ProblemList from './problem/ProblemList.vue' import Logout from './user/Logout.vue' import UserHome from './user/UserHome.vue' import About from './help/About.vue' import FAQ from './help/FAQ.vue' import NotFound from './general/404.vue' import Home from './general/Home.vue' import Announcements from './general/Announcements.vue' // Grouping Components in the Same Chunk const SubmissionList = () => import(/* webpackChunkName: "submission" */ '@oj/views/submission/SubmissionList.vue') const SubmissionDetails = () => import(/* webpackChunkName: "submission" */ '@oj/views/submission/SubmissionDetails.vue') const ACMRank = () => import(/* webpackChunkName: "userRank" */ '@oj/views/rank/ACMRank.vue') const OIRank = () => import(/* webpackChunkName: "userRank" */ '@oj/views/rank/OIRank.vue') const ApplyResetPassword = () => import(/* webpackChunkName: "password" */ '@oj/views/user/ApplyResetPassword.vue') const ResetPassword = () => import(/* webpackChunkName: "password" */ '@oj/views/user/ResetPassword.vue') const Problem = () => import(/* webpackChunkName: "Problem" */ '@oj/views/problem/Problem.vue') export { Home, NotFound, Announcements, Logout, UserHome, About, FAQ, ProblemList, Problem, ACMRank, OIRank, SubmissionList, SubmissionDetails, ApplyResetPassword, ResetPassword } /* 组件导出分为两类, 一类常用的直接导出,另一类诸如Login, Logout等用懒加载,懒加载不在此处导出 * 在对应的route内加载 * 见https://router.vuejs.org/en/advanced/lazy-loading.html */ ================================================ FILE: src/pages/oj/views/problem/Problem.vue ================================================ ================================================ FILE: src/pages/oj/views/problem/ProblemList.vue ================================================ ================================================ FILE: src/pages/oj/views/problem/chartData.js ================================================ const pieColorMap = { 'AC': {color: '#19be6b'}, 'WA': {color: '#ed3f14'}, 'TLE': {color: '#ff9300'}, 'MLE': {color: '#f7de00'}, 'RE': {color: '#ff6104'}, 'CE': {color: '#80848f'}, 'PAC': {color: '#2d8cf0'} } function getItemColor (obj) { return pieColorMap[obj.name].color } const pie = { legend: { left: 'center', top: '10', orient: 'horizontal', data: ['AC', 'WA'] }, series: [ { name: 'Summary', type: 'pie', radius: '80%', center: ['50%', '55%'], itemStyle: { normal: {color: getItemColor} }, data: [ {value: 0, name: 'WA'}, {value: 0, name: 'AC'} ], label: { normal: { position: 'inner', show: true, formatter: '{b}: {c}\n {d}%', textStyle: { fontWeight: 'bold' } } } } ] } const largePie = { legend: { left: 'center', top: '10', orient: 'horizontal', itemGap: 20, data: ['AC', 'RE', 'WA', 'TLE', 'PAC', 'MLE'] }, series: [ { name: 'Detail', type: 'pie', radius: ['45%', '70%'], center: ['50%', '55%'], itemStyle: { normal: {color: getItemColor} }, data: [ {value: 0, name: 'RE'}, {value: 0, name: 'WA'}, {value: 0, name: 'TLE'}, {value: 0, name: 'AC'}, {value: 0, name: 'MLE'}, {value: 0, name: 'PAC'} ], label: { normal: { formatter: '{b}: {c}\n {d}%' } }, labelLine: { normal: {} } }, { name: 'Summary', type: 'pie', radius: '30%', center: ['50%', '55%'], itemStyle: { normal: {color: getItemColor} }, data: [ {value: '0', name: 'WA'}, {value: 0, name: 'AC', selected: true} ], label: { normal: { position: 'inner', formatter: '{b}: {c}\n {d}%' } } } ] } export { pie, largePie } ================================================ FILE: src/pages/oj/views/rank/ACMRank.vue ================================================ ================================================ FILE: src/pages/oj/views/rank/OIRank.vue ================================================ ================================================ FILE: src/pages/oj/views/setting/Settings.vue ================================================ ================================================ FILE: src/pages/oj/views/setting/children/AccountSetting.vue ================================================ ================================================ FILE: src/pages/oj/views/setting/children/ProfileSetting.vue ================================================ ================================================ FILE: src/pages/oj/views/setting/children/SecuritySetting.vue ================================================ ================================================ FILE: src/pages/oj/views/setting/index.js ================================================ const Settings = () => import(/* webpackChunkName: "setting" */ './Settings.vue') const ProfileSetting = () => import(/* webpackChunkName: "setting" */ './children/ProfileSetting.vue') const SecuritySetting = () => import(/* webpackChunkName: "setting" */ './children/SecuritySetting.vue') const AccountSetting = () => import(/* webpackChunkName: "setting" */ './children/AccountSetting.vue') export {Settings, ProfileSetting, SecuritySetting, AccountSetting} ================================================ FILE: src/pages/oj/views/submission/SubmissionDetails.vue ================================================ ================================================ FILE: src/pages/oj/views/submission/SubmissionList.vue ================================================ ================================================ FILE: src/pages/oj/views/user/ApplyResetPassword.vue ================================================ ================================================ FILE: src/pages/oj/views/user/Login.vue ================================================ ================================================ FILE: src/pages/oj/views/user/Logout.vue ================================================ ================================================ FILE: src/pages/oj/views/user/Register.vue ================================================ ================================================ FILE: src/pages/oj/views/user/ResetPassword.vue ================================================ ================================================ FILE: src/pages/oj/views/user/UserHome.vue ================================================ ================================================ FILE: src/plugins/highlight.js ================================================ import hljs from 'highlight.js/lib/highlight' import cpp from 'highlight.js/lib/languages/cpp' import python from 'highlight.js/lib/languages/python' import java from 'highlight.js/lib/languages/java' import 'highlight.js/styles/atom-one-light.css' hljs.registerLanguage('cpp', cpp) hljs.registerLanguage('java', java) hljs.registerLanguage('python', python) export default { install (Vue, options) { Vue.directive('highlight', { deep: true, bind: function (el, binding) { // on first bind, highlight all targets Array.from(el.querySelectorAll('code')).forEach((target) => { // if a value is directly assigned to the directive, use this // instead of the element content. if (binding.value) { target.textContent = binding.value } hljs.highlightBlock(target) }) }, componentUpdated: function (el, binding) { // after an update, re-fill the content and then highlight Array.from(el.querySelectorAll('code')).forEach((target) => { if (binding.value) { target.textContent = binding.value } hljs.highlightBlock(target) }) } }) } } ================================================ FILE: src/plugins/katex.js ================================================ import 'katex' import renderMathInElement from 'katex/contrib/auto-render/auto-render' import 'katex/dist/katex.min.css' function _ () { } const defaultOptions = { errorCallback: _, throwOnError: false, delimiters: [ {left: '$$', right: '$$', display: true}, {left: '$', right: '$', display: false}, {left: '\\[', right: '\\]', display: true}, {left: '\\(', right: '\\)', display: false} ] } function render (el, binding) { let options = {} if (binding.value) { options = binding.value.options || {} } Object.assign(options, defaultOptions) renderMathInElement(el, options) } export default { install: function (Vue, options) { Vue.directive('katex', { bind: render, componentUpdated: render }) } } ================================================ FILE: src/store/index.js ================================================ import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import contest from './modules/contest' import api from '@oj/api' import types from './types' Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' const rootState = { website: {}, modalStatus: { mode: 'login', // or 'register', visible: false } } const rootGetters = { 'website' (state) { return state.website }, 'modalStatus' (state) { return state.modalStatus } } const rootMutations = { [types.UPDATE_WEBSITE_CONF] (state, payload) { state.website = payload.websiteConfig }, [types.CHANGE_MODAL_STATUS] (state, {mode, visible}) { if (mode !== undefined) { state.modalStatus.mode = mode } if (visible !== undefined) { state.modalStatus.visible = visible } } } const rootActions = { getWebsiteConfig ({commit}) { api.getWebsiteConf().then(res => { commit(types.UPDATE_WEBSITE_CONF, { websiteConfig: res.data.data }) }) }, changeModalStatus ({commit}, payload) { commit(types.CHANGE_MODAL_STATUS, payload) }, changeDomTitle ({commit, state}, payload) { if (payload && payload.title) { window.document.title = state.website.website_name_shortcut + ' | ' + payload.title } else { window.document.title = state.website.website_name_shortcut + ' | ' + state.route.meta.title } } } export default new Vuex.Store({ modules: { user, contest }, state: rootState, getters: rootGetters, mutations: rootMutations, actions: rootActions, strict: debug }) export { types } ================================================ FILE: src/store/modules/contest.js ================================================ import moment from 'moment' import types from '../types' import api from '@oj/api' import { CONTEST_STATUS, USER_TYPE, CONTEST_TYPE } from '@/utils/constants' const state = { now: moment(), access: false, rankLimit: 30, forceUpdate: false, contest: { created_by: {}, contest_type: CONTEST_TYPE.PUBLIC }, contestProblems: [], itemVisible: { menu: true, chart: true, realName: false } } const getters = { // contest 是否加载完成 contestLoaded: (state) => { return !!state.contest.status }, contestStatus: (state, getters) => { if (!getters.contestLoaded) return null let startTime = moment(state.contest.start_time) let endTime = moment(state.contest.end_time) let now = state.now if (startTime > now) { return CONTEST_STATUS.NOT_START } else if (endTime < now) { return CONTEST_STATUS.ENDED } else { return CONTEST_STATUS.UNDERWAY } }, contestRuleType: (state) => { return state.contest.rule_type || null }, isContestAdmin: (state, getters, _, rootGetters) => { return rootGetters.isAuthenticated && (state.contest.created_by.id === rootGetters.user.id || rootGetters.user.admin_type === USER_TYPE.SUPER_ADMIN) }, contestMenuDisabled: (state, getters) => { if (getters.isContestAdmin) return false if (state.contest.contest_type === CONTEST_TYPE.PUBLIC) { return getters.contestStatus === CONTEST_STATUS.NOT_START } return !state.access }, OIContestRealTimePermission: (state, getters, _, rootGetters) => { if (getters.contestRuleType === 'ACM' || getters.contestStatus === CONTEST_STATUS.ENDED) { return true } return state.contest.real_time_rank === true || getters.isContestAdmin }, problemSubmitDisabled: (state, getters, _, rootGetters) => { if (getters.contestStatus === CONTEST_STATUS.ENDED) { return true } else if (getters.contestStatus === CONTEST_STATUS.NOT_START) { return !getters.isContestAdmin } return !rootGetters.isAuthenticated }, passwordFormVisible: (state, getters) => { return state.contest.contest_type !== CONTEST_TYPE.PUBLIC && !state.access && !getters.isContestAdmin }, contestStartTime: (state) => { return moment(state.contest.start_time) }, contestEndTime: (state) => { return moment(state.contest.end_time) }, countdown: (state, getters) => { if (getters.contestStatus === CONTEST_STATUS.NOT_START) { let duration = moment.duration(getters.contestStartTime.diff(state.now, 'seconds'), 'seconds') // time is too long if (duration.weeks() > 0) { return 'Start At ' + duration.humanize() } let texts = [Math.floor(duration.asHours()), duration.minutes(), duration.seconds()] return '-' + texts.join(':') } else if (getters.contestStatus === CONTEST_STATUS.UNDERWAY) { let duration = moment.duration(getters.contestEndTime.diff(state.now, 'seconds'), 'seconds') let texts = [Math.floor(duration.asHours()), duration.minutes(), duration.seconds()] return '-' + texts.join(':') } else { return 'Ended' } } } const mutations = { [types.CHANGE_CONTEST] (state, payload) { state.contest = payload.contest }, [types.CHANGE_CONTEST_ITEM_VISIBLE] (state, payload) { state.itemVisible = {...state.itemVisible, ...payload} }, [types.CHANGE_RANK_FORCE_UPDATE] (state, payload) { state.forceUpdate = payload.value }, [types.CHANGE_CONTEST_PROBLEMS] (state, payload) { state.contestProblems = payload.contestProblems }, [types.CHANGE_CONTEST_RANK_LIMIT] (state, payload) { state.rankLimit = payload.rankLimit }, [types.CONTEST_ACCESS] (state, payload) { state.access = payload.access }, [types.CLEAR_CONTEST] (state) { state.contest = {created_by: {}} state.contestProblems = [] state.access = false state.itemVisible = { menu: true, chart: true, realName: false } state.forceUpdate = false }, [types.NOW] (state, payload) { state.now = payload.now }, [types.NOW_ADD_1S] (state) { state.now = moment(state.now.add(1, 's')) } } const actions = { getContest ({commit, rootState, dispatch}) { return new Promise((resolve, reject) => { api.getContest(rootState.route.params.contestID).then((res) => { resolve(res) let contest = res.data.data commit(types.CHANGE_CONTEST, {contest: contest}) commit(types.NOW, {now: moment(contest.now)}) if (contest.contest_type === CONTEST_TYPE.PRIVATE) { dispatch('getContestAccess') } }, err => { reject(err) }) }) }, getContestProblems ({commit, rootState}) { return new Promise((resolve, reject) => { api.getContestProblemList(rootState.route.params.contestID).then(res => { res.data.data.sort((a, b) => { if (a._id === b._id) { return 0 } else if (a._id > b._id) { return 1 } return -1 }) commit(types.CHANGE_CONTEST_PROBLEMS, {contestProblems: res.data.data}) resolve(res) }, () => { commit(types.CHANGE_CONTEST_PROBLEMS, {contestProblems: []}) }) }) }, getContestAccess ({commit, rootState}) { return new Promise((resolve, reject) => { api.getContestAccess(rootState.route.params.contestID).then(res => { commit(types.CONTEST_ACCESS, {access: res.data.data.access}) resolve(res) }).catch() }) } } export default { state, mutations, getters, actions } ================================================ FILE: src/store/modules/user.js ================================================ import types from '../types' import api from '@oj/api' import storage from '@/utils/storage' import i18n from '@/i18n' import { STORAGE_KEY, USER_TYPE, PROBLEM_PERMISSION } from '@/utils/constants' const state = { profile: {} } const getters = { user: state => state.profile.user || {}, profile: state => state.profile, isAuthenticated: (state, getters) => { return !!getters.user.id }, isAdminRole: (state, getters) => { return getters.user.admin_type === USER_TYPE.ADMIN || getters.user.admin_type === USER_TYPE.SUPER_ADMIN }, isSuperAdmin: (state, getters) => { return getters.user.admin_type === USER_TYPE.SUPER_ADMIN }, hasProblemPermission: (state, getters) => { return getters.user.problem_permission !== PROBLEM_PERMISSION.NONE } } const mutations = { [types.CHANGE_PROFILE] (state, {profile}) { state.profile = profile if (profile.language) { i18n.locale = profile.language } storage.set(STORAGE_KEY.AUTHED, !!profile.user) } } const actions = { getProfile ({commit}) { api.getUserInfo().then(res => { commit(types.CHANGE_PROFILE, { profile: res.data.data || {} }) }) }, clearProfile ({commit}) { commit(types.CHANGE_PROFILE, { profile: {} }) storage.clear() } } export default { state, getters, actions, mutations } ================================================ FILE: src/store/types.js ================================================ function keyMirror (obj) { if (obj instanceof Object) { var _obj = Object.assign({}, obj) var _keyArray = Object.keys(obj) _keyArray.forEach(key => { _obj[key] = key }) return _obj } } export default keyMirror({ 'CHANGE_PROFILE': null, 'CHANGE_MODAL_STATUS': null, 'UPDATE_WEBSITE_CONF': null, 'NOW': null, 'NOW_ADD_1S': null, 'CHANGE_CONTEST': null, 'CHANGE_CONTEST_PROBLEMS': null, 'CHANGE_CONTEST_ITEM_VISIBLE': null, 'CHANGE_RANK_FORCE_UPDATE': null, 'CHANGE_CONTEST_RANK_LIMIT': null, 'CONTEST_ACCESS': null, 'CLEAR_CONTEST': null }) ================================================ FILE: src/styles/common.less ================================================ html { background-color: #eee; } body { margin: 0; padding: 0; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; -webkit-font-smoothing: antialiased; background-color: #eee; min-width: 900px; } .flex-container { display: flex; width: 100%; max-width: 100%; justify-content: space-around; align-items: flex-start; flex-flow: row nowrap; } .section-title { font-size: 21px; font-weight: 500; padding-top: 10px; padding-bottom: 20px; line-height: 30px; } .separator { display: block; position: absolute; top: 0; bottom: 0; left: 50%; border: 1px dashed #eee; } .oj-captcha { display: flex; flex-wrap: nowrap; justify-content: space-between; width: 100%; height: 36px; .oj-captcha-code { flex: auto; } .oj-captcha-img { margin-left: 10px; padding: 3px; flex: initial; } } .oj-relative { position: relative; } a.emphasis{ color: #495060; &:hover { color: #2d8cf0; } } // for mathjax .MathJax { outline: 0; } .MathJax_Display { overflow-x: auto; overflow-y: hidden; } .markdown-body { @import './markdown.less'; } @keyframes fadeInUp { from { opacity: 0; transform: translate(0, 50px); } to { opacity: 1; transform: none; } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } ================================================ FILE: src/styles/index.less ================================================ @import './common.less'; @import './iview-custom.less'; ================================================ FILE: src/styles/iview-custom.less ================================================ table { width: 100% !important; } .auto-resize { table { table-layout: auto !important; } } .ivu-table-wrapper { border: none; } .ivu-table td { border-bottom-color: #dddddd; } .ivu-card-head { border-bottom-width: 0; } .ivu-table td { border-bottom-color: #dddddd; } .ivu-table .first-ac { background-color: #33CC99; color: #3c763d; } .ivu-table .ac { background-color: #dff0d8;; color: #3c763d; } .ivu-table .wa { color: #a94442; background-color: #f2dede; } .ivu-modal-footer { border-top-width: 0; padding: 0 18px 20px 18px; } .ivu-modal-body { padding: 18px; } ================================================ FILE: src/styles/markdown.less ================================================ h1, h2, h3, h4 { color: #111111; font-weight: 400; } h1, h2, h3, h4, h5, p { margin-bottom: 15px; padding: 0; } h1 { font-size: 28px; } h2 { font-size: 24px; } h3 { font-size: 20px; } h4 { font-size: 18px; } h5 { font-size: 14px; } a { color: #0099ff; margin: 0; padding: 0; vertical-align: baseline; } ul, ol { padding: 0; margin: 10px 20px; } ul { list-style-type: disc; } ol { list-style-type: decimal; } li { line-height: 24px; } li ul, li ul { margin-left: 24px; } p, ul, ol { font-size: 16px; line-height: 24px; } pre { padding: 5px 10px; white-space: pre-wrap; margin-top: 15px; margin-bottom: 15px; background: #f8f8f9; border: 1px dashed #e9eaec; } code { font-size: 90%; padding: 2px 5px; margin: 0; background-color: rgba(27, 31, 35, 0.05); border-radius: 3px; line-height: 1.5; } pre > code { padding: 0; margin: 0; font-size: 100%; word-break: normal; white-space: pre; background: transparent; border: 0; } aside { display: block; float: right; width: 390px; } blockquote { border-left: 3px solid #bbbec4; padding-left: 10px; margin-top: 10px; margin-bottom: 10px; color: #7b7b7b; } hr { width: 540px; text-align: left; margin: 0 auto 0 0; color: #999; } table { border-collapse: collapse; margin: 1em 1em; border: 1px solid #ccc; thead { background-color: #eee; td { color: #666; } } td { padding: 0.5em 1em; border: 1px solid #ccc; } } ================================================ FILE: src/utils/constants.js ================================================ export const JUDGE_STATUS = { '-2': { name: 'Compile Error', short: 'CE', color: 'yellow', type: 'warning' }, '-1': { name: 'Wrong Answer', short: 'WA', color: 'red', type: 'error' }, '0': { name: 'Accepted', short: 'AC', color: 'green', type: 'success' }, '1': { name: 'Time Limit Exceeded', short: 'TLE', color: 'red', type: 'error' }, '2': { name: 'Time Limit Exceeded', short: 'TLE', color: 'red', type: 'error' }, '3': { name: 'Memory Limit Exceeded', short: 'MLE', color: 'red', type: 'error' }, '4': { name: 'Runtime Error', short: 'RE', color: 'red', type: 'error' }, '5': { name: 'System Error', short: 'SE', color: 'red', type: 'error' }, '6': { name: 'Pending', color: 'yellow', type: 'warning' }, '7': { name: 'Judging', color: 'blue', type: 'info' }, '8': { name: 'Partial Accepted', short: 'PAC', color: 'blue', type: 'info' }, '9': { name: 'Submitting', color: 'yellow', type: 'warning' } } export const CONTEST_STATUS = { 'NOT_START': '1', 'UNDERWAY': '0', 'ENDED': '-1' } export const CONTEST_STATUS_REVERSE = { '1': { name: 'Not Started', color: 'yellow' }, '0': { name: 'Underway', color: 'green' }, '-1': { name: 'Ended', color: 'red' } } export const RULE_TYPE = { ACM: 'ACM', OI: 'OI' } export const CONTEST_TYPE = { PUBLIC: 'Public', PRIVATE: 'Password Protected' } export const USER_TYPE = { REGULAR_USER: 'Regular User', ADMIN: 'Admin', SUPER_ADMIN: 'Super Admin' } export const PROBLEM_PERMISSION = { NONE: 'None', OWN: 'Own', ALL: 'All' } export const STORAGE_KEY = { AUTHED: 'authed', PROBLEM_CODE: 'problemCode', languages: 'languages' } export function buildProblemCodeKey (problemID, contestID = null) { if (contestID) { return `${STORAGE_KEY.PROBLEM_CODE}_${contestID}_${problemID}` } return `${STORAGE_KEY.PROBLEM_CODE}_NaN_${problemID}` } export const GOOGLE_ANALYTICS_ID = 'UA-111499601-1' ================================================ FILE: src/utils/filters.js ================================================ import moment from 'moment' import utils from './utils' import time from './time' // 友好显示时间 function fromNow (time) { return moment(time * 3).fromNow() } export default { submissionMemory: utils.submissionMemoryFormat, submissionTime: utils.submissionTimeFormat, localtime: time.utcToLocal, fromNow: fromNow } ================================================ FILE: src/utils/sentry.js ================================================ import Vue from 'vue' import Raven from 'raven-js' import RavenVue from 'raven-js/plugins/vue' const options = { release: process.env.VERSION, ignoreUrls: [ // Chrome extensions /extensions\//i, /^chrome:\/\//i, // Firefox extensions /^resource:\/\//i, // Ignore Google flakiness /\/(gtm|ga|analytics)\.js/i ] } Raven .config('https://6234a51e61a743b089ed64c51d2f6ea9@sentry.io/258234', options) .addPlugin(RavenVue, Vue) .install() Raven.setUserContext({ version: process.env.VERSION, location: window.location }) ================================================ FILE: src/utils/storage.js ================================================ const localStorage = window.localStorage export default { name: 'storage', /** * save value(Object) to key * @param {string} key 键 * @param {Object} value 值 */ set (key, value) { localStorage.setItem(key, JSON.stringify(value)) }, /** * get value(Object) by key * @param {string} key 键 * @return {Object} */ get (key) { return JSON.parse(localStorage.getItem(key)) || null }, /** * remove key from localStorage * @param {string} key 键 */ remove (key) { localStorage.removeItem(key) }, /** * clear all */ clear () { localStorage.clear() } } ================================================ FILE: src/utils/time.js ================================================ import moment from 'moment' // convert utc time to localtime function utcToLocal (utcDt, format = 'YYYY-M-D HH:mm:ss') { return moment.utc(utcDt).local().format(format) } // get duration from startTime to endTime, return like 3 days, 2 hours, one year .. function duration (startTime, endTime) { let start = moment(startTime) let end = moment(endTime) let duration = moment.duration(start.diff(end, 'seconds'), 'seconds') if (duration.days() !== 0) { return duration.humanize() } return Math.abs(duration.asHours().toFixed(1)) + ' hours' } function secondFormat (seconds) { let m = moment.duration(seconds, 'seconds') return Math.floor(m.asHours()) + ':' + m.minutes() + ':' + m.seconds() } export default { utcToLocal: utcToLocal, duration: duration, secondFormat: secondFormat } ================================================ FILE: src/utils/utils.js ================================================ import Vue from 'vue' import storage from '@/utils/storage' import { STORAGE_KEY } from '@/utils/constants' import ojAPI from '@oj/api' function submissionMemoryFormat (memory) { if (memory === undefined) return '--' // 1048576 = 1024 * 1024 let t = parseInt(memory) / 1048576 return String(t.toFixed(0)) + 'MB' } function submissionTimeFormat (time) { if (time === undefined) return '--' return time + 'ms' } function getACRate (acCount, totalCount) { let rate = totalCount === 0 ? 0.00 : (acCount / totalCount * 100).toFixed(2) return String(rate) + '%' } // 去掉值为空的项,返回object function filterEmptyValue (object) { let query = {} Object.keys(object).forEach(key => { if (object[key] || object[key] === 0 || object[key] === false) { query[key] = object[key] } }) return query } // 按指定字符数截断添加换行,非英文字符按指定字符的半数截断 function breakLongWords (value, length = 16) { let re if (escape(value).indexOf('%u') === -1) { // 没有中文 re = new RegExp('(.{' + length + '})', 'g') } else { // 中文字符 re = new RegExp('(.{' + (length / 2 + 1) + '})', 'g') } return value.replace(re, '$1\n') } function downloadFile (url) { return new Promise((resolve, reject) => { Vue.prototype.$http.get(url, {responseType: 'blob'}).then(resp => { let headers = resp.headers if (headers['content-type'].indexOf('json') !== -1) { let fr = new window.FileReader() if (resp.data.error) { Vue.prototype.$error(resp.data.error) } else { Vue.prototype.$error('Invalid file format') } fr.onload = (event) => { let data = JSON.parse(event.target.result) if (data.error) { Vue.prototype.$error(data.data) } else { Vue.prototype.$error('Invalid file format') } } let b = new window.Blob([resp.data], {type: 'application/json'}) fr.readAsText(b) return } let link = document.createElement('a') link.href = window.URL.createObjectURL(new window.Blob([resp.data], {type: headers['content-type']})) link.download = (headers['content-disposition'] || '').split('filename=')[1] document.body.appendChild(link) link.click() link.remove() resolve() }).catch(() => {}) }) } function getLanguages () { return new Promise((resolve, reject) => { let languages = storage.get(STORAGE_KEY.languages) if (languages) { resolve(languages) } ojAPI.getLanguages().then(res => { let languages = res.data.data.languages storage.set(STORAGE_KEY.languages, languages) resolve(languages) }, err => { reject(err) }) }) } export default { submissionMemoryFormat: submissionMemoryFormat, submissionTimeFormat: submissionTimeFormat, getACRate: getACRate, filterEmptyValue: filterEmptyValue, breakLongWords: breakLongWords, downloadFile: downloadFile, getLanguages: getLanguages } ================================================ FILE: static/css/loader.css ================================================ @-webkit-keyframes enter { 0% { opacity: 0; top: -10px; } 5% { opacity: 1; top: 0px; } 50.9% { opacity: 1; top: 0px; } 55.9% { opacity: 0; top: 10px; } } @keyframes enter { 0% { opacity: 0; top: -10px; } 5% { opacity: 1; top: 0px; } 50.9% { opacity: 1; top: 0px; } 55.9% { opacity: 0; top: 10px; } } @-moz-keyframes enter { 0% { opacity: 0; top: -10px; } 5% { opacity: 1; top: 0px; } 50.9% { opacity: 1; top: 0px; } 55.9% { opacity: 0; top: 10px; } } body { background: #f8f8f9; } #app-loader { position: absolute; left: 50%; top: 50%; margin-left: -27.5px; margin-top: -27.5px; } #app-loader .square { background: #2d8cf0; width: 15px; height: 15px; float: left; top: -10px; margin-right: 5px; margin-top: 5px; position: relative; opacity: 0; -webkit-animation: enter 6s infinite; animation: enter 6s infinite; } #app-loader .enter { top: 0px; opacity: 1; } #app-loader .square:nth-child(1) { -webkit-animation-delay: 1.8s; -moz-animation-delay: 1.8s; animation-delay: 1.8s; } #app-loader .square:nth-child(2) { -webkit-animation-delay: 2.1s; -moz-animation-delay: 2.1s; animation-delay: 2.1s; } #app-loader .square:nth-child(3) { -webkit-animation-delay: 2.4s; -moz-animation-delay: 2.4s; animation-delay: 2.4s; background: #ff9900; } #app-loader .square:nth-child(4) { -webkit-animation-delay: 0.9s; -moz-animation-delay: 0.9s; animation-delay: 0.9s; } #app-loader .square:nth-child(5) { -webkit-animation-delay: 1.2s; -moz-animation-delay: 1.2s; animation-delay: 1.2s; } #app-loader .square:nth-child(6) { -webkit-animation-delay: 1.5s; -moz-animation-delay: 1.5s; animation-delay: 1.5s; } #app-loader .square:nth-child(8) { -webkit-animation-delay: 0.3s; -moz-animation-delay: 0.3s; animation-delay: 0.3s; } #app-loader .square:nth-child(9) { -webkit-animation-delay: 0.6s; -moz-animation-delay: 0.6s; animation-delay: 0.6s; } #app-loader .clear { clear: both; } #app-loader .last { margin-right: 0; }