add login view; fix bugs in add contest problem.

This commit is contained in:
zema1 2017-10-25 18:02:37 +08:00
parent 06e007801a
commit 2b11b7117b
22 changed files with 555 additions and 401 deletions

View File

@ -7,7 +7,7 @@ module.exports = {
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/admin',
assetsPublicPath: '/admin/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.

View File

@ -11,6 +11,7 @@
"lint": "eslint --ext .js,.vue src"
},
"dependencies": {
"axios": "^0.17.0",
"element-ui": "^1.3.7",
"font-awesome": "^4.7.0",
"moment": "^2.18.1",
@ -19,7 +20,6 @@
"vue": "^2.3.3",
"vue-codemirror": "^3.1.0",
"vue-i18n": "^7.0.5",
"vue-resource": "^1.3.4",
"vue-router": "^2.6.0"
},
"devDependencies": {

View File

@ -1,80 +1,31 @@
<template>
<div>
<div>
<SideMenu></SideMenu>
</div>
<div class="content-app">
<router-view></router-view>
<div class="footer">
Build Version: {{ version }}
</div>
</div>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
import 'font-awesome/css/font-awesome.min.css'
import SideMenu from './components/SideMenu.vue'
import api from './api.js'
export default {
name: 'app',
data () {
return {
version: process.env.VERSION
}
},
components: {
SideMenu
},
methods: {},
mounted () {
api.login('root', 'rootroot').then(res => {})
}
components: {}
}
</script>
<style lang="less">
body {
margin: 0;
background-color: rgb(236, 242, 247)!important;
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects
}
a:active, a:hover {
outline-width: 0
}
img {
border-style: none
}
body {
padding: 0;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
overflow: auto;
font-weight: 400;
font-size: 14px;
-webkit-font-smoothing: antialiased;
background-color: #EDECEC;
overflow-y: scroll;
background-color: #324157;
}
* {
box-sizing: border-box;
}
.content-app {
padding-top: 20px;
padding-right:10px;
padding-left: 210px;
}
.footer {
margin: 15px;
text-align: center;
font-size: small;
#app {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>

View File

@ -1,65 +1,50 @@
import Vue from 'vue'
import VueResource from 'vue-resource'
import router from './router'
import axios from 'axios'
Vue.use(VueResource)
Vue.http.options.root = '/api'
Vue.http.options.emulateJSON = false
function getCookie (name) {
let allCookies = document.cookie.split('; ')
for (let i = 0; i < allCookies.length; i++) {
let cookie = allCookies[i].split('=')
if (cookie[0] === name) {
return cookie[1]
} else {
return ''
}
}
}
Vue.http.interceptors.push((request, next) => {
request.headers.set('X-CSRFToken', getCookie('csrftoken'))
next()
})
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', 'get', {
options: {
params: {
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', {
options: {
params: {
paging: true,
offset,
limit
}
params: {
paging: true,
offset,
limit
}
})
},
// 删除公告
deleteAnnouncement (id) {
return ajax('admin/announcement', 'delete', {
options: {
params: {
id
}
params: {
id
}
})
},
// 修改公告
modifyAnnouncement (id, title, content, visible) {
return ajax('admin/announcement', 'put', {
body: {
data: {
id,
title,
content,
@ -70,7 +55,7 @@ export default {
// 添加公告
createAnnouncement (title, content, visible) {
return ajax('admin/announcement', 'post', {
body: {
data: {
title,
content,
visible
@ -92,17 +77,15 @@ export default {
// 获取单个用户信息
getUser (id) {
return ajax('admin/user', 'get', {
options: {
params: {
id
}
params: {
id
}
})
},
// 编辑用户
editUser (body) {
editUser (data) {
return ajax('admin/user', 'put', {
body
data
})
},
getLanguages () {
@ -111,22 +94,22 @@ export default {
getSMTPConfig () {
return ajax('admin/smtp', 'get')
},
createSMTPConfig (body) {
createSMTPConfig (data) {
return ajax('admin/smtp', 'post', {
body
data
})
},
editSMTPConfig (body) {
editSMTPConfig (data) {
return ajax('admin/smtp', 'put', {
body
data
})
},
getWebsiteConfig () {
return ajax('admin/website', 'get')
},
editWebsiteConfig (config) {
editWebsiteConfig (data) {
return ajax('admin/website', 'post', {
body: config
data
})
},
getJudgeServer () {
@ -134,30 +117,26 @@ export default {
},
deleteJudgeServer (hostname) {
return ajax('admin/judge_server', 'delete', {
options: {
params: {
hostname: hostname
}
params: {
hostname: hostname
}
})
},
createContest (body) {
createContest (data) {
return ajax('admin/contest', 'post', {
body: body
data
})
},
getContest (id) {
return ajax('admin/contest', 'get', {
options: {
params: {
id
}
params: {
id
}
})
},
editContest (body) {
editContest (data) {
return ajax('admin/contest', 'put', {
body
data
})
},
getContestList (offset, limit, keyword) {
@ -166,53 +145,45 @@ export default {
params.keyword = keyword
}
return ajax('admin/contest', 'get', {
options: {
params: params
}
params: params
})
},
getContestAnnouncementList (contestId) {
return ajax('admin/contest/announcement', 'get', {
options: {
params: {
contest_id: contestId
}
params: {
contest_id: contestId
}
})
},
createContestAnnouncement (body) {
createContestAnnouncement (data) {
return ajax('admin/contest/announcement', 'post', {
body
data
})
},
deleteContestAnnouncement (id) {
return ajax('admin/contest/announcement', 'delete', {
options: {
params: {
id
}
params: {
id
}
})
},
getProblemTagList () {
return ajax('problem/tags', 'get')
},
createProblem (body) {
createProblem (data) {
return ajax('admin/problem', 'post', {
body
data
})
},
editProblem (body) {
editProblem (data) {
return ajax('admin/problem', 'put', {
body
data
})
},
getProblem (id) {
return ajax('admin/problem', 'get', {
options: {
params: {
id
}
params: {
id
}
})
},
@ -222,9 +193,7 @@ export default {
params.keyword = keyword
}
return ajax('admin/problem', 'get', {
options: {
params: params
}
params
})
},
getContestProblemList (offset, limit, keyword, contestId) {
@ -233,76 +202,66 @@ export default {
params.keyword = keyword
}
return ajax('admin/contest/problem', 'get', {
options: {
params: params
}
params
})
},
getContestProblem (id) {
return ajax('admin/contest/problem', 'get', {
options: {
params: {
id
}
params: {
id
}
})
},
createContestProblem (body) {
createContestProblem (data) {
return ajax('admin/contest/problem', 'post', {
body
data
})
},
editContestProblem (body) {
editContestProblem (data) {
return ajax('admin/contest/problem', 'put', {
body
data
})
}
}
/**
ajax 请求
@param url
@param type get|post|put|jsonp ....
@param options options = {
body: request body
options: ..,
succCallBack: Function
errCallBack: Function
}
@return Promise
*/
function ajax (url, type, options) {
return new Promise(function (resolve, reject) {
options = options || {}
if (options.body === undefined) {
options.body = options.options
options.options = undefined
}
Vue.http[type](url, options.body, options.options).then(res => {
// 出错了
/**
* @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)
if (options.errCallBack !== undefined) {
options.errCallBack(res)
// // 若后端返回为登录则为session失效应退出当前登录用户
if (res.data.data.startsWith('Please login')) {
router.push({name: 'login'})
}
} else {
// 请求成功
resolve(res)
if (options.succCallBack !== undefined) {
options.succCallBack(res)
} else if (type !== 'get') {
Vue.prototype.$success()
if (method !== 'get') {
Vue.prototype.$success('Succeeded')
}
}
}, res => {
// 请求失败
// API请求异常一般为Server error 或 network error
reject(res)
if (options.errCallBack !== undefined) {
options.errCallBack(res)
} else {
Vue.prototype.$error('Network Error')
}
Vue.prototype.$error(res.data.data)
})
})
}

View File

@ -42,6 +42,7 @@ export default{
width: 200px;
height: 100%;
position: fixed!important;
z-index: 100;
top: 0;
bottom: 0;
left: 0;

View File

@ -2,10 +2,10 @@ import Vue from 'vue'
import App from './App'
import Element from 'element-ui'
import 'element-ui/lib/theme-default/index.css'
import VueRouter from 'vue-router'
import locale from 'element-ui/lib/locale/lang/en'
import filters from './filters.js'
import filters from './utils/filters.js'
import router from './router'
import Panel from './components/Panel.vue'
import IconBtn from './components/btn/IconBtn.vue'
@ -17,98 +17,13 @@ Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
Vue.use(Element, { locale })
Vue.use(VueRouter)
Vue.use(Element, {locale})
// Vue.use(VueI18n)
Vue.component(IconBtn.name, IconBtn)
Vue.component(Panel.name, Panel)
Vue.component(Save.name, Save)
Vue.component(Cancel.name, Cancel)
// 引入 view 组件
import { Announcement, User, Conf, JudgeServer, Problem, Contest, ContestList,
ContestAnnouncement, ProblemList } from './views'
const router = new VueRouter({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: [
{
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: '/problems',
name: 'problem-list',
component: ProblemList
},
{
path: '/problem/create',
name: 'create-problem',
component: Problem
},
{
path: '/problem/edit/:problemId',
name: 'edit-problem',
component: Problem
},
{
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: ContestAnnouncement
},
{
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: '/announcement'
}
]
})
Vue.prototype.$error = (msg) => {
Vue.prototype.$message({'message': msg, 'type': 'error'})
}
@ -125,4 +40,4 @@ Vue.prototype.$success = (msg) => {
}
}
new Vue(Vue.util.extend({ router }, App)).$mount('#app')
new Vue(Vue.util.extend({router}, App)).$mount('#app')

113
admin/src/router.js Normal file
View File

@ -0,0 +1,113 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
// 引入 view 组件
import {
Announcement,
Conf,
Contest,
ContestAnnouncement,
ContestList,
Home,
JudgeServer,
Login,
Problem,
ProblemList,
User
} 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: '',
component: Announcement
},
{
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: '/problems',
name: 'problem-list',
component: ProblemList
},
{
path: '/problem/create',
name: 'create-problem',
component: Problem
},
{
path: '/problem/edit/:problemId',
name: 'edit-problem',
component: Problem
},
{
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: ContestAnnouncement
},
{
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: '/problem-list'
}
]
}]
})

View File

View File

@ -0,0 +1,5 @@
export const CONTEST_STATUS_REVERSE = {
'-1': 'Ended',
'0': 'Underway',
'1': 'Not Started'
}

111
admin/src/views/Home.vue Normal file
View File

@ -0,0 +1,111 @@
<template>
<div class="container">
<div>
<SideMenu></SideMenu>
</div>
<div id="header">
<el-dropdown @command="handleCommand">
<span>{{profile.user.username}}<i class="el-icon-caret-bottom el-icon--right"></i></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="logout" >Logout</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="content-app">
<router-view></router-view>
<div class="footer">
Build Version: {{ version }}
</div>
</div>
</div>
</template>
<script>
import 'font-awesome/css/font-awesome.min.css'
import SideMenu from '../components/SideMenu.vue'
import api from '../api'
export default {
name: 'app',
data () {
return {
version: process.env.VERSION,
profile: {
user: {}
}
}
},
components: {
SideMenu
},
beforeRouteEnter (to, from, next) {
api.getProfile().then(res => {
if (!res.data.data) {
// not login
next({name: 'login'})
} else {
next(vm => {
vm.profile = res.data.data
})
}
})
},
methods: {
handleCommand (command) {
if (command === 'logout') {
api.logout().then(() => {
this.$router.push({name: 'login'})
})
}
}
}
}
</script>
<style lang="less">
a {
background-color: transparent;
}
a:active, a:hover {
outline-width: 0
}
img {
border-style: none
}
.container {
overflow: auto;
font-weight: 400;
height: 100%;
-webkit-font-smoothing: antialiased;
background-color: #EDECEC;
overflow-y: scroll;
}
* {
box-sizing: border-box;
}
#header {
text-align: right;
padding-left: 210px;
padding-right: 30px;
line-height: 50px;
height: 50px;
background: #F9FAFC;
}
.content-app {
padding-top: 20px;
padding-right: 10px;
padding-left: 210px;
}
.footer {
margin: 15px;
text-align: center;
font-size: small;
}
</style>

View File

@ -15,7 +15,7 @@
:data="contestList"
style="width: 100%">
<el-table-column type="expand">
<template scope="props">
<template slot-scope="props">
<div>
<el-tag :type="props.row.visible ? 'success' : 'danger'">{{props.row.visible ? 'Visible' : 'Invisible'}}
</el-tag>
@ -23,8 +23,8 @@
{{ props.row.contest_type}}
</el-tag>
<el-tag
:type="props.row.status === 'Ended' ? 'gray' : props.row.status === 'Underway' ? 'success' : 'primary'">
{{ props.row.status }}
:type="props.row.status === '-1' ? 'gray' : props.row.status === '0' ? 'success' : 'primary'">
{{ props.row.status | contestStatus}}
</el-tag>
</div>
<p>
@ -43,14 +43,14 @@
<el-table-column
prop="start_time"
label="Start Time">
<template scope="scope">
<template slot-scope="scope">
{{scope.row.start_time | localtime }}
</template>
</el-table-column>
<el-table-column
prop="end_time"
label="End Time">
<template scope="scope">
<template slot-scope="scope">
{{scope.row.end_time | localtime }}
</template>
</el-table-column>
@ -85,6 +85,7 @@
<script>
import api from '../../api.js'
import {CONTEST_STATUS_REVERSE} from '../../utils/constants'
export default {
name: 'ContestList',
@ -101,6 +102,11 @@
mounted () {
this.getContestList(this.currentPage)
},
filters: {
contestStatus (value) {
return CONTEST_STATUS_REVERSE[value]
}
},
methods: {
//
currentChange (page) {

View File

@ -19,14 +19,14 @@
<el-table-column
prop="create_time"
label="CreateTime">
<template scope="scope">
<template slot-scope="scope">
{{ scope.row.create_time | localtime }}
</template>
</el-table-column>
<el-table-column
prop="last_update_time"
label="LastUpdateTime">
<template scope="scope">
<template slot-scope="scope">
{{scope.row.last_update_time | localtime }}
</template>
</el-table-column>

View File

@ -10,7 +10,7 @@
border>
<el-table-column
type="expand">
<template scope="props">
<template slot-scope="props">
<p>IP:
<el-tag type="success">{{ props.row.ip }}</el-tag>&nbsp;&nbsp;
Judger Version:
@ -25,7 +25,7 @@
<el-table-column
prop="status"
label="Status">
<template scope="scope">
<template slot-scope="scope">
<el-tag
:type="scope.row.status === 'normal' ? 'success' : 'danger'">
{{ scope.row.status === 'normal' ? 'Normal' : 'Abnormal' }}
@ -47,17 +47,17 @@
<el-table-column
prop="cpu_usage"
label="CPU Usage">
<template scope="scope">{{ scope.row.cpu_usage }}%</template>
<template slot-scope="scope">{{ scope.row.cpu_usage }}%</template>
</el-table-column>
<el-table-column
prop="memory_usage"
label="Memory Usage">
<template scope="scope">{{ scope.row.memory_usage }}%</template>
<template slot-scope="scope">{{ scope.row.memory_usage }}%</template>
</el-table-column>
<el-table-column
fixed="right"
label="option">
<template scope="scope">
<template slot-scope="scope">
<icon-btn name="Delete" icon="trash" @click.native="deleteJudgeServer(scope.row.hostname)"></icon-btn>
</template>
</el-table-column>

View File

@ -17,19 +17,19 @@
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="username" label="Userame"></el-table-column>
<el-table-column prop="create_time" label="Create Time">
<template scope="scope">
<template slot-scope="scope">
{{scope.row.create_time | localtime }}
</template>
</el-table-column>
<el-table-column prop="last_login" label="Last Login">
<template scope="scope">
<template slot-scope="scope">
{{scope.row.last_login | localtime }}
</template>
</el-table-column>
<el-table-column prop="real_name" label="Real Name"></el-table-column>
<el-table-column prop="email" label="Email"></el-table-column>
<el-table-column prop="admin_type" label="User Type">
<template scope="scope">
<template slot-scope="scope">
{{ scope.row.admin_type }}
</template>
</el-table-column>

View File

@ -0,0 +1,83 @@
<template>
<el-form :model="ruleForm2" :rules="rules2" ref="ruleForm2" label-position="left" label-width="0px"
class="demo-ruleForm login-container">
<h3 class="title">Welcome to Login</h3>
<el-form-item prop="account">
<el-input type="text" v-model="ruleForm2.account" auto-complete="off" placeholder="username"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="ruleForm2.password" auto-complete="off" placeholder="password"></el-input>
</el-form-item>
<el-form-item style="width:100%;">
<el-button type="primary" style="width:100%;" @click.native.prevent="handleLogin" :loading="logining">GO
</el-button>
</el-form-item>
</el-form>
</template>
<script>
import api from '../../api'
export default {
data () {
return {
logining: false,
ruleForm2: {
account: 'root',
password: 'rootroot'
},
rules2: {
account: [
{required: true, trigger: 'blur'}
],
password: [
{required: true, trigger: 'blur'}
]
},
checked: true
}
},
methods: {
handleLogin (ev) {
this.$refs.ruleForm2.validate((valid) => {
if (valid) {
this.logining = true
api.login(this.ruleForm2.account, this.ruleForm2.password).then(data => {
this.logining = false
this.$router.push({name: 'problem-list'})
}, () => {
this.logining = false
})
} else {
this.$error('Please check the error fields')
}
})
}
}
}
</script>
<style lang="less" scoped>
.login-container {
/*box-shadow: 0 0px 8px 0 rgba(0, 0, 0, 0.06), 0 1px 0px 0 rgba(0, 0, 0, 0.02);*/
-webkit-border-radius: 5px;
border-radius: 5px;
-moz-border-radius: 5px;
background-clip: padding-box;
margin: 180px auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
.title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.remember {
margin: 0px 0px 35px 0px;
}
}
</style>

View File

@ -7,8 +7,10 @@ import ProblemList from './problem/ProblemList.vue'
import ContestList from './contest/ContestList.vue'
import Contest from './contest/Contest.vue'
import ContestAnnouncement from './contest/Announcement.vue'
import Login from './general/login.vue'
import Home from './Home.vue'
export {
Announcement, User, Conf, JudgeServer, Problem, ProblemList, Contest,
ContestList, ContestAnnouncement
ContestList, ContestAnnouncement, Login, Home
}

View File

@ -15,7 +15,8 @@
<el-form ref="form" :model="problem" :rules="rules" label-position="top" label-width="70px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item prop="_id" label="Display ID" :required="this.routeName === 'create-contest-problem' || this.routeName === 'edit-contet-problem'">
<el-form-item prop="_id" label="Display ID"
:required="this.routeName === 'create-contest-problem' || this.routeName === 'edit-contet-problem'">
<el-input placeholder="Display ID" v-model="problem._id"></el-input>
</el-form-item>
</el-col>
@ -113,7 +114,8 @@
<el-col :span="8">
<el-form-item label="Languages" :error="error.languages" required>
<el-checkbox-group v-model="problem.languages">
<el-tooltip class="spj-radio" v-for="lang in allLanguage.languages" :key="'spj'+lang.name" effect="dark" :content="lang.description" placement="top-start">
<el-tooltip class="spj-radio" v-for="lang in allLanguage.languages" :key="'spj'+lang.name" effect="dark"
:content="lang.description" placement="top-start">
<el-checkbox :label="lang.name"></el-checkbox>
</el-tooltip>
</el-checkbox-group>
@ -121,36 +123,38 @@
</el-col>
</el-row>
<div>
<el-form-item v-for="(sample, index) in problem.samples" :key="'sample'+index">
<Accordion :title="'Sample' + (index + 1)">
<el-button type="warning" size="small" icon="delete" slot="header" @click="deleteSample(index)">Delete</el-button>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="Input Samples" required>
<el-input
:rows="5"
type="textarea"
placeholder="Input Samples"
v-model="sample.input">
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Output Samples" required>
<el-input
:rows="5"
type="textarea"
placeholder="Output Samples"
v-model="sample.output">
</el-input>
</el-form-item>
</el-col>
</el-row>
</Accordion>
</el-form-item>
<el-form-item v-for="(sample, index) in problem.samples" :key="'sample'+index">
<Accordion :title="'Sample' + (index + 1)">
<el-button type="warning" size="small" icon="delete" slot="header" @click="deleteSample(index)">Delete
</el-button>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="Input Samples" required>
<el-input
:rows="5"
type="textarea"
placeholder="Input Samples"
v-model="sample.input">
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="Output Samples" required>
<el-input
:rows="5"
type="textarea"
placeholder="Output Samples"
v-model="sample.output">
</el-input>
</el-form-item>
</el-col>
</el-row>
</Accordion>
</el-form-item>
</div>
<div class="add-sample-btn">
<button type="button" class="add-samples" @click="addSample()"><i class="el-icon-plus"></i>Add Samples</button>
<button type="button" class="add-samples" @click="addSample()"><i class="el-icon-plus"></i>Add Samples
</button>
</div>
<el-form-item label="Code Template">
<el-row>
@ -172,7 +176,8 @@
<el-col v-if="problem.spj" :span="12">
<el-form-item label="Special Judge Language">
<el-radio-group v-model="problem.spj_language">
<el-tooltip class="spj-radio" v-for="lang in allLanguage.spj_languages" :key="lang.name" effect="dark" :content="lang.description" placement="top-start">
<el-tooltip class="spj-radio" v-for="lang in allLanguage.spj_languages" :key="lang.name" effect="dark"
:content="lang.description" placement="top-start">
<el-radio :label="lang.name">{{ lang.name }}</el-radio>
</el-tooltip>
</el-radio-group>
@ -220,7 +225,7 @@
<el-table-column
prop="score"
label="Score">
<template scope="scope">
<template slot-scope="scope">
<el-input
size="small"
placeholder="Score"
@ -249,7 +254,8 @@
import Accordion from '../../components/Accordion'
import CodeMirror from '../../components/CodeMirror'
import api from '../../api'
export default{
export default {
name: 'Problem',
components: {
Simditor,
@ -259,9 +265,9 @@
data () {
return {
rules: {
title: { required: true, message: 'Title is required', trigger: 'blur' },
input_description: { required: true, message: 'Input Description is required', trigger: 'blur' },
output_description: { required: true, message: 'Output Description is required', trigger: 'blur' }
title: {required: true, message: 'Title is required', trigger: 'blur'},
input_description: {required: true, message: 'Input Description is required', trigger: 'blur'},
output_description: {required: true, message: 'Output Description is required', trigger: 'blur'}
},
mode: '',
problem: {
@ -318,6 +324,14 @@
hint: '',
source: ''
}
let contestID = this.$route.params.contestId
if (contestID) {
this.problem.contest_id = contestID
this.disableRuleType = true
api.getContest(contestID).then(res => {
this.problem.rule_type = this.reProblem.rule_type = res.data.data.rule_type
})
}
this.problem.spj_language = 'C'
@ -326,8 +340,8 @@
// get problem after getting languages list to avoid find undefined value in `watch problem.languages`
if (this.mode === 'edit') {
let funcName = {'edit-problem': 'getProblem', 'edit-contest-problem': 'getContestProblem'}[this.routeName]
this.title = 'Edit Problem'
let funcName = {'edit-problem': 'getProblem', 'edit-contest-problem': 'getContestProblem'}[this.routeName]
api[funcName](this.$route.params.problemId).then(problemRes => {
let data = problemRes.data.data
if (!data.spj_code) {
@ -336,21 +350,12 @@
data.spj_language = data.spj_language || 'C'
this.problem = data
this.testCaseUploaded = true
if (this.routeName === 'edit-contest-problem') {
this.problem.contest_id = this.$route.params.contestId
this.disableRuleType = true
}
})
} else {
this.title = 'Add Problem'
for (let item of allLanguage.languages) {
this.problem.languages.push(item.name)
}
if (this.routeName === 'create-contest-problem') {
this.problem.contest_id = this.$route.params.contestId
this.disableRuleType = true
}
}
})
},
@ -393,7 +398,8 @@
}).then(() => {
this.problem.spj = !this.problem.spj
this.resetTestCase()
}).catch(() => {})
}).catch(() => {
})
} else {
this.problem.spj = !this.problem.spj
}
@ -405,7 +411,8 @@
tagList.push({value: tag.name})
}
cb(tagList)
}).catch(() => {})
}).catch(() => {
})
},
resetTestCase () {
this.testCaseUploaded = false
@ -498,7 +505,8 @@
this.problem.template[k] = this.template[k].code
}
}
let funcName = {'create-problem': 'createProblem',
let funcName = {
'create-problem': 'createProblem',
'edit-problem': 'editProblem',
'create-contest-problem': 'createContestProblem',
'edit-contest-problem': 'editContestProblem'
@ -509,56 +517,57 @@
} else {
this.$router.push({name: 'problem-list'})
}
}).catch(() => {})
}).catch(() => {
})
}
}
}
</script>
<style lang="less" scoped>
.problem{
.difficulty-select{
width: 120px;
}
.spj-radio{
margin-right: 15px;
}
.input-new-tag{
width: 78px;
}
.button-new-tag{
height: 24px;
line-height: 22px;
padding-top: 0;
padding-bottom: 0;
}
.tags{
.el-tag{
margin-right: 10px;
.problem {
.difficulty-select {
width: 120px;
}
.spj-radio {
margin-right: 15px;
}
.input-new-tag {
width: 78px;
}
.button-new-tag {
height: 24px;
line-height: 22px;
padding-top: 0;
padding-bottom: 0;
}
.tags {
.el-tag {
margin-right: 10px;
}
}
.accordion {
margin-bottom: 10px;
}
.add-samples {
width: 100%;
background-color: #fff;
border: 1px dashed #aaa;
outline: none;
cursor: pointer;
color: #666;
height: 35px;
font-size: 14px;
&:hover {
background-color: #f9fafc;
}
i {
margin-right: 10px;
}
}
.add-sample-btn {
margin-bottom: 10px;
}
}
.accordion{
margin-bottom: 10px;
}
.add-samples{
width: 100%;
background-color: #fff;
border: 1px dashed #aaa;
outline: none;
cursor: pointer;
color: #666;
height: 35px;
font-size: 14px;
&:hover {
background-color: #f9fafc;
}
i{
margin-right: 10px;
}
}
.add-sample-btn {
margin-bottom: 10px;
}
}
</style>

View File

@ -33,13 +33,13 @@
<el-table-column
prop="create_time"
label="Create Time">
<template scope="scope">
<template slot-scope="scope">
{{scope.row.create_time | localtime }}
</template>
</el-table-column>
<el-table-column
label="Status">
<template scope="props">
<template slot-scope="props">
<el-tag :type="props.row.visible ? 'success' : 'danger'">{{props.row.visible ? 'Visible' : 'Invisible'}}
</el-tag>
</template>
@ -52,7 +52,6 @@
width="180">
<div>
<icon-btn name="Edit" icon="edit" @click.native="goEdit(row.id)"></icon-btn>
<icon-btn name="Submission" icon="code"></icon-btn>
</div>
</el-table-column>
</el-table>

View File

@ -94,7 +94,7 @@ http {
listen 80 default_server;
server_name _;
location /static/avatar {
location ^~ (/admin)?/static/avatar {
expires max;
alias /data/avatar;
}

View File

@ -10,12 +10,12 @@ run_build() {
exit 1
fi
echo "Build success, dist files in $1/dist\n"
echo -e "Build success, dist files in $1/dist\n"
}
sleep 3
run_build /OJ_FE/oj
# run_build /OJ_FE/oj
run_build /OJ_FE/admin
echo -e "\n\n Congratulations, All have done without error."
echo -e "you can check the onlinejudge with http://IP:80 \n"
exec nginx -c /OJ_FE/deploy/nginx.conf

View File

@ -27,7 +27,7 @@ router.beforeEach((to, from, next) => {
if (!storage.get(STORAGE_KEY.AUTHED)) {
Vue.prototype.$error('please login first')
next({
name: 'test'
name: 'home'
})
} else {
next()