优化加载本地保存的代码的逻辑; 部分细节优化

This commit is contained in:
zema1 2017-11-13 16:51:08 +08:00
parent 1910fc3bb0
commit 67c3b97be8
14 changed files with 173 additions and 152 deletions

View File

@ -1,11 +1,11 @@
language: node_js language: node_js
node_js: node_js:
- "6.2.2" - "6.11.2"
env: env:
- CXX=g++-4.8 - CXX=g++-4.8
script: script:
- cd admin
- npm install - npm install
- npm run build:dll
- npm run build - npm run build
addons: addons:
apt: apt:

View File

@ -32,10 +32,9 @@ rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
process.exit(1) process.exit(1)
} }
console.log(chalk.cyan(' Build complete.\n')) console.log(chalk.cyan(' Congratulations, the project build complete without error\n'))
console.log(chalk.yellow( console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' + ' You can now check the onlinejudge in http://YouIP/'
' Opening index.html over file:// won\'t work.\n'
)) ))
}) })
}) })

View File

@ -19,15 +19,11 @@
</Tag> </Tag>
</div> </div>
<div v-html="contest.description"></div> <div v-html="contest.description"></div>
<div v-if="passwordFormVisible"> <div v-if="passwordFormVisible" class="contest-password">
<Form inline class="contest-password"> <Input v-model="contestPassword" type="password"
<FormItem> placeholder="contest password" class="contest-password-input"
<Input v-model="contestPassword" type="password" placeholder="contest password"/> @on-enter="checkPassword" />
</FormItem> <Button type="info" @click="checkPassword">Enter</Button>
<FormItem>
<Button type="info" @click="checkPassword">Enter</Button>
</FormItem>
</Form>
</div> </div>
</Panel> </Panel>
<Table :columns="columns" :data="contest_table" disabled-hover style="margin-bottom: 40px;"></Table> <Table :columns="columns" :data="contest_table" disabled-hover style="margin-bottom: 40px;"></Table>
@ -61,7 +57,7 @@
:disabled="contestMenuDisabled" :disabled="contestMenuDisabled"
:route="{name: 'contest-rank', params: {contestID: contestID}}"> :route="{name: 'contest-rank', params: {contestID: contestID}}">
<Icon type="stats-bars"></Icon> <Icon type="stats-bars"></Icon>
Ranklist Rankings
</VerticalMenu-item> </VerticalMenu-item>
<VerticalMenu-item :route="{name: 'contest-details', params: {contestID: contestID}}"> <VerticalMenu-item :route="{name: 'contest-details', params: {contestID: contestID}}">
@ -205,6 +201,10 @@
.contest-password { .contest-password {
margin-top: 20px; margin-top: 20px;
margin-bottom: -10px; margin-bottom: -10px;
&-input {
width: 200px;
margin-right: 10px;
}
} }
} }
</style> </style>

View File

@ -13,7 +13,7 @@
</p> </p>
<p style="margin-top: 10px"> <p style="margin-top: 10px">
<span>Auto Refresh(10s)</span> <span>Auto Refresh(10s)</span>
<i-switch @on-change="handleAutoRefresh"></i-switch> <i-switch :disabled="refreshDisabled" @on-change="handleAutoRefresh"></i-switch>
</p> </p>
</div> </div>
</Poptip> </Poptip>
@ -32,18 +32,20 @@
</template> </template>
<script> <script>
import moment from 'moment' import moment from 'moment'
import Pagination from '@oj/components/Pagination' import { mapActions } from 'vuex'
import { mapActions, mapState } from 'vuex' import Pagination from '@oj/components/Pagination'
import { types } from '@oj/store' import ContestRankMixin from './contestRankMixin'
import api from '@oj/api' import api from '@oj/api'
import time from '@/utils/time' import time from '@/utils/time'
import utils from '@/utils/utils'
export default { export default {
name: 'acm-contest-rank', name: 'acm-contest-rank',
components: { components: {
Pagination Pagination
}, },
mixins: [ContestRankMixin],
data () { data () {
return { return {
total: 0, total: 0,
@ -60,7 +62,6 @@
{ {
title: 'User', title: 'User',
align: 'center', align: 'center',
width: 250,
render: (h, params) => { render: (h, params) => {
return h('Button', { return h('Button', {
props: { props: {
@ -69,6 +70,9 @@
'class': { 'class': {
'link-button': true 'link-button': true
}, },
style: {
'max-width': '150px'
},
on: { on: {
click: () => { click: () => {
this.$router.push( this.$router.push(
@ -132,7 +136,7 @@
right: 0, right: 0,
data: [], data: [],
formatter: (value) => { formatter: (value) => {
return value.replace(/(.{16})/g, '$1\n') return utils.breakLongWords(value, 16)
}, },
textStyle: { textStyle: {
fontSize: 12 fontSize: 12
@ -140,7 +144,7 @@
}, },
grid: { grid: {
x: 80, x: 80,
x2: 250 x2: 200
}, },
xAxis: [{ xAxis: [{
type: 'time', type: 'time',
@ -266,6 +270,9 @@
type: 'text', type: 'text',
size: 'large' size: 'large'
}, },
style: {
padding: 0
},
on: { on: {
click: () => { click: () => {
this.$router.push({ this.$router.push({
@ -294,55 +301,7 @@
} }
}) })
}) })
},
handleAutoRefresh (status) {
if (status === true) {
this.refreshFunc = setInterval(() => {
this.getContestRankData(1, true)
}, 10000)
} else {
clearInterval(this.refreshFunc)
}
} }
},
computed: {
...mapState({
'contest': state => state.contest.contest,
'contestProblems': state => state.contest.contestProblems
}),
showChart: {
get () {
return this.$store.state.contest.showChart
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_CHART_VISIBLE, {visible: value})
}
},
showMenu: {
get () {
return this.$store.state.contest.showMenu
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_MENU_VISIBLE, {visible: value})
this.$nextTick(() => {
this.$refs.tableRank.handleResize()
if (this.showChart) {
this.$refs.chart.resize()
}
})
}
},
limit: {
get () {
return this.$store.state.contest.rankLimit
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_RANK_LIMIT, {rankLimit: value})
}
}
},
beforeDestroy () {
clearInterval(this.refreshFunc)
} }
} }
</script> </script>

View File

@ -13,7 +13,7 @@
</p> </p>
<p style="margin-top: 10px"> <p style="margin-top: 10px">
<span>Auto Refresh(10s)</span> <span>Auto Refresh(10s)</span>
<i-switch @on-change="handleAutoRefresh"></i-switch> <i-switch :disabled="refreshDisabled" @on-change="handleAutoRefresh"></i-switch>
</p> </p>
</div> </div>
</Poptip> </Poptip>
@ -31,10 +31,11 @@
</Panel> </Panel>
</template> </template>
<script> <script>
import Pagination from '@oj/components/Pagination' import { mapActions } from 'vuex'
import { mapActions, mapState } from 'vuex' import Pagination from '@oj/components/Pagination'
import { types } from '@oj/store' import ContestRankMixin from './contestRankMixin'
import utils from '@/utils/utils'
import api from '@oj/api' import api from '@oj/api'
export default { export default {
@ -42,6 +43,7 @@
components: { components: {
Pagination Pagination
}, },
mixins: [ContestRankMixin],
data () { data () {
return { return {
total: 0, total: 0,
@ -58,7 +60,6 @@
{ {
title: 'User', title: 'User',
align: 'center', align: 'center',
width: 250,
render: (h, params) => { render: (h, params) => {
return h('Button', { return h('Button', {
props: { props: {
@ -67,6 +68,9 @@
'class': { 'class': {
'link-button': true 'link-button': true
}, },
style: {
'max-width': '150px'
},
on: { on: {
click: () => { click: () => {
this.$router.push( this.$router.push(
@ -116,7 +120,7 @@
showMaxLabel: true, showMaxLabel: true,
align: 'center', align: 'center',
formatter: (value, index) => { formatter: (value, index) => {
return value.replace(/(.{8})/g, '$1\n') return utils.breakLongWords(value, 14)
} }
}, },
axisTick: { axisTick: {
@ -207,6 +211,9 @@
type: 'text', type: 'text',
size: 'large' size: 'large'
}, },
style: {
padding: 0
},
on: { on: {
click: () => { click: () => {
this.$router.push({ this.$router.push({
@ -225,55 +232,7 @@
} }
}) })
}) })
},
handleAutoRefresh (status) {
if (status === true) {
this.refreshFunc = setInterval(() => {
this.getContestRankData(1, true)
}, 10000)
} else {
clearInterval(this.refreshFunc)
}
} }
},
computed: {
...mapState({
'contest': state => state.contest.contest,
'contestProblems': state => state.contest.contestProblems
}),
showChart: {
get () {
return this.$store.state.contest.showChart
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_CHART_VISIBLE, {visible: value})
}
},
showMenu: {
get () {
return this.$store.state.contest.showMenu
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_MENU_VISIBLE, {visible: value})
this.$nextTick(() => {
this.$refs.tableRank.handleResize()
if (this.showChart) {
this.$refs.chart.resize()
}
})
}
},
limit: {
get () {
return this.$store.state.contest.rankLimit
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_RANK_LIMIT, {rankLimit: value})
}
}
},
beforeDestroy () {
clearInterval(this.refreshFunc)
} }
} }
</script> </script>

View File

@ -0,0 +1,60 @@
import { mapState } from 'vuex'
import { types } from '@oj/store/index'
import { CONTEST_STATUS } from '@/utils/constants'
export default {
methods: {
handleAutoRefresh (status) {
if (status === true) {
this.refreshFunc = setInterval(() => {
this.page = 1
this.getContestRankData(1, true)
}, 10000)
} else {
clearInterval(this.refreshFunc)
}
}
},
computed: {
...mapState({
'contest': state => state.contest.contest,
'contestProblems': state => state.contest.contestProblems
}),
showChart: {
get () {
return this.$store.state.contest.showChart
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_CHART_VISIBLE, {visible: value})
}
},
showMenu: {
get () {
return this.$store.state.contest.showMenu
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_MENU_VISIBLE, {visible: value})
this.$nextTick(() => {
this.$refs.tableRank.handleResize()
if (this.showChart) {
this.$refs.chart.resize()
}
})
}
},
refreshDisabled () {
return this.contest.status === CONTEST_STATUS.ENDED
},
limit: {
get () {
return this.$store.state.contest.rankLimit
},
set (value) {
this.$store.commit(types.CHANGE_CONTEST_RANK_LIMIT, {rankLimit: value})
}
}
},
beforeDestroy () {
clearInterval(this.refreshFunc)
}
}

View File

@ -26,6 +26,7 @@
</CarouselItem> </CarouselItem>
</Carousel> </Carousel>
</panel> </panel>
<Announcements class="announcement"></Announcements> <Announcements class="announcement"></Announcements>
</Col> </Col>
</Row> </Row>

View File

@ -105,7 +105,7 @@
<VerticalMenu-item v-if="!this.contestID || OIContestRealTimePermission" <VerticalMenu-item v-if="!this.contestID || OIContestRealTimePermission"
:route="{name: 'contest-rank', params: {contestID: contestID}}"> :route="{name: 'contest-rank', params: {contestID: contestID}}">
<Icon type="stats-bars"></Icon> <Icon type="stats-bars"></Icon>
Ranklist Rankings
</VerticalMenu-item> </VerticalMenu-item>
<VerticalMenu-item :route="{name: 'contest-details', params: {contestID: contestID}}"> <VerticalMenu-item :route="{name: 'contest-details', params: {contestID: contestID}}">
<Icon type="home"></Icon> <Icon type="home"></Icon>
@ -184,7 +184,7 @@
import storage from '@/utils/storage' import storage from '@/utils/storage'
import { FormMixin } from '@oj/components/mixins' import { FormMixin } from '@oj/components/mixins'
import { types } from '@oj/store' import { types } from '@oj/store'
import { JUDGE_STATUS, CONTEST_STATUS, STORAGE_KEY } from '@/utils/constants' import { JUDGE_STATUS, CONTEST_STATUS, buildProblemCodeKey } from '@/utils/constants'
import api from '@oj/api' import api from '@oj/api'
import { pie, largePie } from './chartData' import { pie, largePie } from './chartData'
@ -231,6 +231,16 @@
} }
} }
}, },
beforeRouteEnter (to, from, next) {
let problemCode = storage.get(buildProblemCodeKey(to.params.problemID, to.params.contestID))
if (problemCode) {
next(vm => {
vm.language = problemCode.language
vm.code = problemCode.code
})
}
next()
},
mounted () { mounted () {
this.$store.commit(types.CHANGE_CONTEST_MENU_VISIBLE, {visible: false}) this.$store.commit(types.CHANGE_CONTEST_MENU_VISIBLE, {visible: false})
this.init() this.init()
@ -245,16 +255,13 @@
this.$Loading.finish() this.$Loading.finish()
this.problem = res.data.data this.problem = res.data.data
this.changePie(res.data.data) this.changePie(res.data.data)
let problemCode = storage.get(STORAGE_KEY.PROBLEM_CODE + this.problem.id)
// beforeRouteEnter, code template
if (this.language !== 'C++' || this.code !== '') {
return
}
let template = this.problem.template let template = this.problem.template
if (problemCode) { if (template && template[this.language]) {
this.language = problemCode.language
if (problemCode.code === '' && template[this.language]) {
this.code = template[this.language]
} else {
this.code = problemCode.code
}
} else if (template[this.language]) {
this.code = template[this.language] this.code = template[this.language]
} }
this.$nextTick(() => { this.$nextTick(() => {
@ -391,8 +398,9 @@
beforeDestroy () { beforeDestroy () {
// //
clearInterval(this.refreshStatus) clearInterval(this.refreshStatus)
this.$store.commit(types.CHANGE_CONTEST_MENU_VISIBLE, {visible: true}) this.$store.commit(types.CHANGE_CONTEST_MENU_VISIBLE, {visible: true})
storage.set(STORAGE_KEY.PROBLEM_CODE + this.problem.id, { storage.set(buildProblemCodeKey(this.problem._id, this.$route.params.contestID), {
code: this.code, code: this.code,
language: this.language language: this.language
}) })

View File

@ -36,6 +36,7 @@
<Table style="width: 100%; font-size: 16px;" <Table style="width: 100%; font-size: 16px;"
:columns="problemTableColumns" :columns="problemTableColumns"
:data="problemList" :data="problemList"
:loading="loadings.table"
disabled-hover></Table> disabled-hover></Table>
</Panel> </Panel>
<Pagination :total="total" :page-size="limit" @on-change="pushRouter" :current.sync="query.page"></Pagination> <Pagination :total="total" :page-size="limit" @on-change="pushRouter" :current.sync="query.page"></Pagination>
@ -59,7 +60,7 @@
Pick one Pick one
</Button> </Button>
</Panel> </Panel>
<Spin v-if="spinShow" fix size="large"></Spin> <Spin v-if="loadings.tag" fix size="large"></Spin>
</Col> </Col>
</Row> </Row>
</template> </template>
@ -145,16 +146,17 @@
problemList: [], problemList: [],
limit: 15, limit: 15,
total: 0, total: 0,
problemLoading: false, loadings: {
tagLoading: false, table: true,
tag: true
},
routeName: '', routeName: '',
query: { query: {
keyword: '', keyword: '',
difficulty: '', difficulty: '',
tag: '', tag: '',
page: 1 page: 1
}, }
spinShow: true
} }
}, },
mounted () { mounted () {
@ -181,21 +183,24 @@
}, },
getProblemList () { getProblemList () {
let offset = (this.query.page - 1) * this.limit let offset = (this.query.page - 1) * this.limit
this.loadings.table = true
api.getProblemList(offset, this.limit, this.query).then(res => { api.getProblemList(offset, this.limit, this.query).then(res => {
this.loadings.table = false
this.total = res.data.data.total this.total = res.data.data.total
this.problemList = res.data.data.results this.problemList = res.data.data.results
if (this.isAuthenticated) { if (this.isAuthenticated) {
this.addStatusColumn(this.problemTableColumns, res.data.data.results) this.addStatusColumn(this.problemTableColumns, res.data.data.results)
} }
}, res => { }, res => {
this.loadings.table = false
}) })
}, },
getTagList () { getTagList () {
api.getProblemTagList().then(res => { api.getProblemTagList().then(res => {
this.tagList = res.data.data this.tagList = res.data.data
this.spinShow = false this.loadings.tag = false
}, res => { }, res => {
this.spinShow = false this.loadings.tag = false
}) })
}, },
filterByTag (tagName) { filterByTag (tagName) {

View File

@ -44,15 +44,17 @@
{ {
title: 'user', title: 'user',
align: 'center', align: 'center',
width: 250,
render: (h, params) => { render: (h, params) => {
return h('Button', { return h('Button', {
props: { props: {
type: 'text' type: 'text'
}, },
class: { 'class': {
'link-button': true 'link-button': true
}, },
style: {
'max-width': '200px'
},
on: { on: {
click: () => { click: () => {
this.$router.push( this.$router.push(
@ -82,6 +84,7 @@
}, },
{ {
title: 'Rating', title: 'Rating',
align: 'center',
render: (h, params) => { render: (h, params) => {
return h('span', utils.getACRate(params.row.accepted_number, params.row.submission_number)) return h('span', utils.getACRate(params.row.accepted_number, params.row.submission_number))
} }
@ -118,7 +121,7 @@
showMaxLabel: true, showMaxLabel: true,
align: 'center', align: 'center',
formatter: (value, index) => { formatter: (value, index) => {
return value.replace(/(.{8})/g, '$1\n') return utils.breakLongWords(value, 10)
} }
} }
} }
@ -165,7 +168,7 @@
api.getUserRank(offset, this.limit, RULE_TYPE.ACM).then(res => { api.getUserRank(offset, this.limit, RULE_TYPE.ACM).then(res => {
this.loadingTable = false this.loadingTable = false
if (page === 1) { if (page === 1) {
this.changeCharts(res.data.data.results) this.changeCharts(res.data.data.results.slice(0, 10))
} }
this.total = res.data.data.total this.total = res.data.data.total
this.dataRank = res.data.data.results this.dataRank = res.data.data.results

View File

@ -43,7 +43,6 @@
{ {
title: 'user', title: 'user',
align: 'center', align: 'center',
width: 250,
render: (h, params) => { render: (h, params) => {
return h('Button', { return h('Button', {
props: { props: {
@ -52,6 +51,9 @@
'class': { 'class': {
'link-button': true 'link-button': true
}, },
style: {
'max-width': '200px'
},
on: { on: {
click: () => { click: () => {
this.$router.push( this.$router.push(
@ -86,6 +88,7 @@
}, },
{ {
title: 'Rating', title: 'Rating',
align: 'center',
render: (h, params) => { render: (h, params) => {
return h('span', utils.getACRate(params.row.accepted_number, params.row.submission_number)) return h('span', utils.getACRate(params.row.accepted_number, params.row.submission_number))
} }
@ -123,7 +126,7 @@
showMaxLabel: true, showMaxLabel: true,
align: 'center', align: 'center',
formatter: (value, index) => { formatter: (value, index) => {
return value.replace(/(.{8})/g, '$1\n') return utils.breakLongWords(value, 14)
} }
}, },
axisTick: { axisTick: {
@ -162,7 +165,7 @@
bar.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'}) bar.showLoading({maskColor: 'rgba(250, 250, 250, 0.8)'})
api.getUserRank(offset, this.limit, RULE_TYPE.OI).then(res => { api.getUserRank(offset, this.limit, RULE_TYPE.OI).then(res => {
if (page === 1) { if (page === 1) {
this.changeCharts(res.data.data.results) this.changeCharts(res.data.data.results.slice(0, 10))
} }
this.total = res.data.data.total this.total = res.data.data.total
this.dataRank = res.data.data.results this.dataRank = res.data.data.results

View File

@ -147,7 +147,6 @@
{ {
title: 'Author', title: 'Author',
align: 'center', align: 'center',
width: 250,
render: (h, params) => { render: (h, params) => {
return h('Button', { return h('Button', {
props: { props: {
@ -156,6 +155,9 @@
'class': { 'class': {
'link-button': true 'link-button': true
}, },
style: {
'max-width': '200px'
},
on: { on: {
click: () => { click: () => {
this.$router.push( this.$router.push(

View File

@ -112,3 +112,10 @@ export const STORAGE_KEY = {
PROBLEM_CODE: 'problem_code_', PROBLEM_CODE: 'problem_code_',
languages: 'languages' languages: 'languages'
} }
export function buildProblemCodeKey (problemID, contestID = null) {
if (contestID) {
return `${STORAGE_KEY.PROBLEM_CODE}_${contestID}_${problemID}`
}
return `${STORAGE_KEY.PROBLEM_CODE}_NaN_${problemID}`
}

View File

@ -25,9 +25,24 @@ function filterEmptyValue (object) {
}) })
return query 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')
}
export default { export default {
submissionMemoryFormat: submissionMemoryFormat, submissionMemoryFormat: submissionMemoryFormat,
submissionTimeFormat: submissionTimeFormat, submissionTimeFormat: submissionTimeFormat,
getACRate: getACRate, getACRate: getACRate,
filterEmptyValue: filterEmptyValue filterEmptyValue: filterEmptyValue,
breakLongWords: breakLongWords
} }