Compare commits
3
Commits
365ce0a7b4
...
61ec7d7f8e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61ec7d7f8e | ||
|
|
6bc927402e | ||
|
|
9b4c4a73f1 |
No files matched your search
@@ -208,6 +208,36 @@ submissionRoutes.get("/submissions/today-count", async (c) => {
|
|||||||
return success(c, row?.value ?? 0)
|
return success(c, row?.value ?? 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 近半年交过代码或流程图的班级,给提交列表的班级下拉补上网站配置里没有的那些。
|
||||||
|
* 配置的班级列表管的是「注册时能选哪些班」,毕业、换届的班从里面摘掉之后
|
||||||
|
* 还在交(生产上 247 摘了之后四个月里交了三百多条),下拉里却选不到它。
|
||||||
|
* 只收 3~4 位数字的班级号,`user.class_name` 里有几个手填的名字不算班。
|
||||||
|
*/
|
||||||
|
submissionRoutes.get("/submissions/classes", requireTeacher, async (c) => {
|
||||||
|
const since = new Date(Date.now() - 180 * 24 * 3600 * 1000).toISOString()
|
||||||
|
const active = db
|
||||||
|
.selectDistinct({ userId: schema.submission.userId })
|
||||||
|
.from(schema.submission)
|
||||||
|
.where(
|
||||||
|
and(isNull(schema.submission.contestId), sql`${schema.submission.createTime} >= ${since}`),
|
||||||
|
)
|
||||||
|
.union(
|
||||||
|
db
|
||||||
|
.selectDistinct({ userId: schema.flowchartSubmission.userId })
|
||||||
|
.from(schema.flowchartSubmission)
|
||||||
|
.where(sql`${schema.flowchartSubmission.createTime} >= ${since}`),
|
||||||
|
)
|
||||||
|
.as("active")
|
||||||
|
const rows = await db
|
||||||
|
.selectDistinct({ className: schema.user.className })
|
||||||
|
.from(active)
|
||||||
|
.innerJoin(schema.user, eq(schema.user.id, active.userId))
|
||||||
|
.where(sql`${schema.user.className} ~ '^[0-9]{3,4}$'`)
|
||||||
|
.orderBy(schema.user.className)
|
||||||
|
return success(c, rows.map((row) => row.className!) satisfies string[])
|
||||||
|
})
|
||||||
|
|
||||||
submissionRoutes.route("/", submissionStatisticsRoutes)
|
submissionRoutes.route("/", submissionStatisticsRoutes)
|
||||||
|
|
||||||
// 老师也能重判:旧栈只给超管,前端却一直给老师亮着按钮,老师点了只会「权限不足」。
|
// 老师也能重判:旧栈只给超管,前端却一直给老师亮着按钮,老师点了只会「权限不足」。
|
||||||
|
|||||||
@@ -167,6 +167,11 @@ export function getTodaySubmissionCount(language?: string) {
|
|||||||
return api.get<number>("submissions/today-count", { params: { language } })
|
return api.get<number>("submissions/today-count", { params: { language } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 近半年有提交的班级(老师用),补上网站配置里已经摘掉的班 */
|
||||||
|
export function getSubmissionClasses() {
|
||||||
|
return api.get<string[]>("submissions/classes")
|
||||||
|
}
|
||||||
|
|
||||||
/** 「今日提交数」标签点开的统计。公开接口,口径同那颗标签:今天 + 非比赛提交 */
|
/** 「今日提交数」标签点开的统计。公开接口,口径同那颗标签:今天 + 非比赛提交 */
|
||||||
export function getTodaySubmissionStatistics() {
|
export function getTodaySubmissionStatistics() {
|
||||||
return api.get<TodaySubmissionStatistics>("submissions/today-statistics")
|
return api.get<TodaySubmissionStatistics>("submissions/today-statistics")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
adminRejudge,
|
adminRejudge,
|
||||||
getClassLesson,
|
getClassLesson,
|
||||||
getFlowchartSubmissions,
|
getFlowchartSubmissions,
|
||||||
|
getSubmissionClasses,
|
||||||
getSubmissions,
|
getSubmissions,
|
||||||
getTodaySubmissionCount,
|
getTodaySubmissionCount,
|
||||||
retryFlowchartSubmission,
|
retryFlowchartSubmission,
|
||||||
@@ -17,7 +18,7 @@ import { useBreakpoints } from "shared/composables/breakpoints"
|
|||||||
import { usePagination } from "shared/composables/pagination"
|
import { usePagination } from "shared/composables/pagination"
|
||||||
import { useConfigStore } from "shared/store/config"
|
import { useConfigStore } from "shared/store/config"
|
||||||
import { useUserStore } from "shared/store/user"
|
import { useUserStore } from "shared/store/user"
|
||||||
import { SubmissionStatus } from "utils/constants"
|
import { LANGUAGE_SHOW_VALUE, SubmissionStatus } from "utils/constants"
|
||||||
import { parseTime } from "utils/functions"
|
import { parseTime } from "utils/functions"
|
||||||
import type {
|
import type {
|
||||||
ClassLesson,
|
ClassLesson,
|
||||||
@@ -444,11 +445,33 @@ const languageOptions = [
|
|||||||
{ label: "SQL", value: "SQL" },
|
{ label: "SQL", value: "SQL" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 配置里的班级列表是「注册时能选的班」,摘掉的老班还在交,得从提交里补出来
|
||||||
|
const activeClasses = ref<string[]>([])
|
||||||
|
watch(
|
||||||
|
teacher,
|
||||||
|
async (isTeacher) => {
|
||||||
|
if (!isTeacher || activeClasses.value.length) return
|
||||||
|
try {
|
||||||
|
activeClasses.value = await getSubmissionClasses()
|
||||||
|
} catch {
|
||||||
|
// 补不上就只有配置里那些,不打扰
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
const classOptions = computed(() => {
|
const classOptions = computed(() => {
|
||||||
const list = configStore.config?.classList ?? []
|
const list = configStore.config?.classList ?? []
|
||||||
// 带进来的班级不在配置里(老班、手打的)也要显示得出来,不然框里是空的却在筛
|
const others = activeClasses.value.filter((item) => !list.includes(item)).reverse()
|
||||||
const all = query.className && !list.includes(query.className) ? [query.className, ...list] : list
|
// 带进来的班级哪边都没有(手打的)也要显示得出来,不然框里是空的却在筛
|
||||||
return all.map((item) => ({ label: classLabel(item), value: item }))
|
if (query.className && !list.includes(query.className) && !others.includes(query.className))
|
||||||
|
others.unshift(query.className)
|
||||||
|
const option = (item: string) => ({ label: classLabel(item), value: item })
|
||||||
|
if (!others.length) return list.map(option)
|
||||||
|
return [
|
||||||
|
{ type: "group", label: "班级列表", key: "listed", children: list.map(option) },
|
||||||
|
{ type: "group", label: "不在班级列表里", key: "others", children: others.map(option) },
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const contestProblemOptions = computed(() => [
|
const contestProblemOptions = computed(() => [
|
||||||
@@ -625,10 +648,12 @@ async function retryFlowchart(id: string) {
|
|||||||
lastPoll = 0
|
lastPoll = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function caseText(row: SubmissionListItem) {
|
/**
|
||||||
const summary = row.caseSummary
|
* 列表上写语言,不写「通过 1/1」:学生把那个数当成了交了几次。
|
||||||
if (!summary) return null
|
* 差几个测试点在右栏状态下面有整句「通过 x/y 个测试点」
|
||||||
return { text: `${summary.passed}/${summary.total}`, full: summary.passed >= summary.total }
|
*/
|
||||||
|
function languageText(row: SubmissionListItem) {
|
||||||
|
return LANGUAGE_SHOW_VALUE[row.language] ?? row.language
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 学生看别人的那几行:名字淡一档、带一把锁(自己的看不了是题单那道闸,另说) */
|
/** 学生看别人的那几行:名字淡一档、带一把锁(自己的看不了是题单那道闸,另说) */
|
||||||
@@ -915,16 +940,7 @@ function dayBreak(index: number) {
|
|||||||
题单
|
题单
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="c-case">
|
<span class="c-lang">{{ languageText(row) }}</span>
|
||||||
<span
|
|
||||||
v-if="caseText(row)"
|
|
||||||
:style="{
|
|
||||||
color: caseText(row)!.full ? theme.textColor3 : tone('error').color,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ caseText(row)!.text }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span class="c-state"><StatusPill :result="row.result" /></span>
|
<span class="c-state"><StatusPill :result="row.result" /></span>
|
||||||
<span v-if="!teacher" class="c-lock">
|
<span v-if="!teacher" class="c-lock">
|
||||||
<!-- title 放在外层 span:挂在 svg 上 Chrome 不出提示 -->
|
<!-- title 放在外层 span:挂在 svg 上 Chrome 不出提示 -->
|
||||||
@@ -947,7 +963,7 @@ function dayBreak(index: number) {
|
|||||||
<span>{{ submissionClockText(row.createTime) }}</span>
|
<span>{{ submissionClockText(row.createTime) }}</span>
|
||||||
<span class="title">{{ row.problemDisplayId }} {{ row.problemTitle }}</span>
|
<span class="title">{{ row.problemDisplayId }} {{ row.problemTitle }}</span>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<span v-if="caseText(row)">{{ caseText(row)!.text }}</span>
|
<span>{{ languageText(row) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -1407,12 +1423,13 @@ function dayBreak(index: number) {
|
|||||||
background: v-bind("tone('info').background");
|
background: v-bind("tone('info').background");
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-case {
|
.c-lang {
|
||||||
width: 34px;
|
width: 48px;
|
||||||
flex: none;
|
flex: none;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-variant-numeric: tabular-nums;
|
white-space: nowrap;
|
||||||
|
color: v-bind("theme.textColor3");
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-state {
|
.c-state {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-flex
|
<n-flex
|
||||||
v-if="!hiddenICP"
|
v-if="route.name === 'home'"
|
||||||
justify="center"
|
justify="center"
|
||||||
align="center"
|
align="center"
|
||||||
:size="isMobile ? 4 : 'medium'"
|
:size="isMobile ? 4 : 'medium'"
|
||||||
@@ -24,10 +24,7 @@ import { zonedYear } from "utils/functions"
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { isMobile } = useBreakpoints()
|
const { isMobile } = useBreakpoints()
|
||||||
// 题目页两栏顶满整屏,三种入口都不放页脚(原来漏了题单入口,页脚压在编辑器底下)
|
// 版权与备案只在首页出现:其余页面都是干活的地方,页脚只占地方
|
||||||
const hiddenICP = computed(() =>
|
|
||||||
["problem", "contest problem", "problemset problem"].includes(route.name as string),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 版权年份也走东八区:站内不留任何一处按浏览器时区取时间部件的代码,
|
// 版权年份也走东八区:站内不留任何一处按浏览器时区取时间部件的代码,
|
||||||
// 免得下一个人照着抄
|
// 免得下一个人照着抄
|
||||||
|
|||||||
Reference in new issue
Block a user