refactor(契约): 展示题号统一叫 problemDisplayId,题单 id 统一叫 problemSetId
Deploy / deploy (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
前后端同时改,部署前开着的旧页面要刷新一次(题号列会是空的、提交筛选会失效)。 - 挂在非题目对象上的展示题号,原来叫 problem(字符串)或 displayId,统一成 problemDisplayId:提交列表、流程图列表、统计的 lastFailure / items / 今日最热题、站内信里的提交、后台题单的题目列表。别处的 problem 都是对象 或数字 id,这几处是字符串,最容易看错 - 列表筛选的 query 参数 ?problemId= 实际传的是展示题号,改成 ?problemDisplayId=(提交列表、流程图列表、三个统计接口) - 契约里 problemsetId / problemSetId、problemset / problemSet 两种拼法并存, 统一成 problemSetId / problemSet,和 ProblemSet* 类型名、problem-sets 路径、前端路由参数一致。drizzle 的列键跟表名走,没动 - aiDetail.user(用户名字符串,而契约里别的 user 都是对象)→ username。 ai_analysis.data 里存的历史快照仍是 user,没有代码读回它 - 后台题单题目表的列是按字符串键 'displayId' 取的,类型检查抓不到,一并改 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5ae830a7f9
commit
9412ba3cf6
24 files changed
+80
-82
No files matched your search
@@ -293,9 +293,9 @@ adminProblemSetRoutes.get(
|
|||||||
({ item, problem }) =>
|
({ item, problem }) =>
|
||||||
({
|
({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
problemsetId: item.problemsetId,
|
problemSetId: item.problemsetId,
|
||||||
problemId: item.problemId,
|
problemId: item.problemId,
|
||||||
displayId: problem.displayId,
|
problemDisplayId: problem.displayId,
|
||||||
title: problem.title,
|
title: problem.title,
|
||||||
difficulty: problem.difficulty,
|
difficulty: problem.difficulty,
|
||||||
order: item.order,
|
order: item.order,
|
||||||
@@ -456,7 +456,7 @@ async function badgesWithCount(badges: BadgeRow[]) {
|
|||||||
(badge) =>
|
(badge) =>
|
||||||
({
|
({
|
||||||
id: badge.id,
|
id: badge.id,
|
||||||
problemsetId: badge.problemsetId,
|
problemSetId: badge.problemsetId,
|
||||||
name: badge.name,
|
name: badge.name,
|
||||||
description: badge.description,
|
description: badge.description,
|
||||||
icon: badge.icon,
|
icon: badge.icon,
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ contentRoutes.get("/messages", requireAuth, async (c) => {
|
|||||||
language: submission.language,
|
language: submission.language,
|
||||||
statisticInfo: asRecord(submission.statisticInfo),
|
statisticInfo: asRecord(submission.statisticInfo),
|
||||||
// 展示用题号而非数字主键,站内信页面拿它拼 /problem/<题号>
|
// 展示用题号而非数字主键,站内信页面拿它拼 /problem/<题号>
|
||||||
problem: displayId,
|
problemDisplayId: displayId,
|
||||||
showLink: true,
|
showLink: true,
|
||||||
} satisfies EmbeddedSubmission,
|
} satisfies EmbeddedSubmission,
|
||||||
}) satisfies Message,
|
}) satisfies Message,
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ flowchartRoutes.get("/flowcharts", requireAuth, async (c) => {
|
|||||||
const user = c.get("user")!
|
const user = c.get("user")!
|
||||||
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })
|
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })
|
||||||
const offset = queryInteger(c.req.query("offset"), 0, { min: 0 })
|
const offset = queryInteger(c.req.query("offset"), 0, { min: 0 })
|
||||||
const displayId = c.req.query("problemId")?.trim()
|
const displayId = c.req.query("problemDisplayId")?.trim()
|
||||||
const username = c.req.query("username")?.trim()
|
const username = c.req.query("username")?.trim()
|
||||||
const grade = c.req.query("grade")
|
const grade = c.req.query("grade")
|
||||||
// 与代码提交列表同一套口径(submission.ts 的 GET /submissions):关掉
|
// 与代码提交列表同一套口径(submission.ts 的 GET /submissions):关掉
|
||||||
@@ -298,7 +298,7 @@ flowchartRoutes.get("/flowcharts", requireAuth, async (c) => {
|
|||||||
({
|
({
|
||||||
id: flowchart.id,
|
id: flowchart.id,
|
||||||
username,
|
username,
|
||||||
problem: problem.displayId,
|
problemDisplayId: problem.displayId,
|
||||||
problemTitle: problem.title,
|
problemTitle: problem.title,
|
||||||
status: flowchart.status,
|
status: flowchart.status,
|
||||||
createTime: flowchart.createTime,
|
createTime: flowchart.createTime,
|
||||||
@@ -349,7 +349,7 @@ flowchartRoutes.get("/flowcharts/statistics", requireTeacher, async (c) => {
|
|||||||
if (start)
|
if (start)
|
||||||
filters.push(sql`${schema.flowchartSubmission.createTime} >= ${start}`)
|
filters.push(sql`${schema.flowchartSubmission.createTime} >= ${start}`)
|
||||||
|
|
||||||
const displayId = c.req.query("problemId")?.trim()
|
const displayId = c.req.query("problemDisplayId")?.trim()
|
||||||
if (displayId) {
|
if (displayId) {
|
||||||
const [problem] = await db
|
const [problem] = await db
|
||||||
.select({ id: schema.problem.id })
|
.select({ id: schema.problem.id })
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ function badgeData(
|
|||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
id: badge.id,
|
id: badge.id,
|
||||||
problemsetId: badge.problemsetId,
|
problemSetId: badge.problemsetId,
|
||||||
name: badge.name,
|
name: badge.name,
|
||||||
description: badge.description,
|
description: badge.description,
|
||||||
icon: badge.icon,
|
icon: badge.icon,
|
||||||
@@ -306,7 +306,7 @@ problemsetRoutes.get("/problem-sets/:id/problems", optionalAuth, async (c) => {
|
|||||||
({ link, problemId, displayId, title, difficulty }) =>
|
({ link, problemId, displayId, title, difficulty }) =>
|
||||||
({
|
({
|
||||||
id: link.id,
|
id: link.id,
|
||||||
problemsetId: link.problemsetId,
|
problemSetId: link.problemsetId,
|
||||||
problem: { id: problemId, _id: displayId, title, difficulty },
|
problem: { id: problemId, _id: displayId, title, difficulty },
|
||||||
order: link.order,
|
order: link.order,
|
||||||
isRequired: link.isRequired,
|
isRequired: link.isRequired,
|
||||||
@@ -435,7 +435,7 @@ problemsetRoutes.get("/users/:username/badges", optionalAuth, async (c) => {
|
|||||||
userId: userBadge.userId,
|
userId: userBadge.userId,
|
||||||
badge: badgeData(badge),
|
badge: badgeData(badge),
|
||||||
earnedTime: userBadge.earnedTime,
|
earnedTime: userBadge.earnedTime,
|
||||||
problemset: { id: problemSet.id, title: problemSet.title },
|
problemSet: { id: problemSet.id, title: problemSet.title },
|
||||||
}) satisfies UserBadge,
|
}) satisfies UserBadge,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -572,7 +572,7 @@ problemsetRoutes.get(
|
|||||||
({ progress, user: progressUser, realName }) =>
|
({ progress, user: progressUser, realName }) =>
|
||||||
({
|
({
|
||||||
id: progress.id,
|
id: progress.id,
|
||||||
problemsetId: progress.problemsetId,
|
problemSetId: progress.problemsetId,
|
||||||
user: sampleUser(progressUser, realName),
|
user: sampleUser(progressUser, realName),
|
||||||
joinTime: progress.joinTime,
|
joinTime: progress.joinTime,
|
||||||
completeTime: progress.completeTime,
|
completeTime: progress.completeTime,
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ submissionStatisticsRoutes.get(
|
|||||||
count: row.value,
|
count: row.value,
|
||||||
})),
|
})),
|
||||||
problems: problemRows.map((row) => ({
|
problems: problemRows.map((row) => ({
|
||||||
problem: row.displayId,
|
problemDisplayId: row.displayId,
|
||||||
problemTitle: row.title,
|
problemTitle: row.title,
|
||||||
count: row.value,
|
count: row.value,
|
||||||
acceptedCount: row.accepted,
|
acceptedCount: row.accepted,
|
||||||
@@ -240,7 +240,7 @@ async function lastFailureByUser(where: SQL | undefined, userIds: number[]) {
|
|||||||
number,
|
number,
|
||||||
{
|
{
|
||||||
id: string
|
id: string
|
||||||
problem: string
|
problemDisplayId: string
|
||||||
result: JudgeStatusValue
|
result: JudgeStatusValue
|
||||||
error: string | null
|
error: string | null
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ async function lastFailureByUser(where: SQL | undefined, userIds: number[]) {
|
|||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
byUser.set(row.user_id, {
|
byUser.set(row.user_id, {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
problem: row.problem,
|
problemDisplayId: row.problem,
|
||||||
result: row.result,
|
result: row.result,
|
||||||
error: row.error,
|
error: row.error,
|
||||||
})
|
})
|
||||||
@@ -412,7 +412,7 @@ async function statisticsScope(c: {
|
|||||||
if (range.start)
|
if (range.start)
|
||||||
filters.push(sql`${schema.submission.createTime} >= ${range.start}`)
|
filters.push(sql`${schema.submission.createTime} >= ${range.start}`)
|
||||||
|
|
||||||
const displayIds = parseDisplayIds(c.req.query("problemId") ?? "")
|
const displayIds = parseDisplayIds(c.req.query("problemDisplayId") ?? "")
|
||||||
if (displayIds.length > STATISTICS_MAX_PROBLEMS) {
|
if (displayIds.length > STATISTICS_MAX_PROBLEMS) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -682,7 +682,7 @@ submissionStatisticsRoutes.get(
|
|||||||
id: schema.submission.id,
|
id: schema.submission.id,
|
||||||
result: schema.submission.result,
|
result: schema.submission.result,
|
||||||
createTime: schema.submission.createTime,
|
createTime: schema.submission.createTime,
|
||||||
problem: schema.problem.displayId,
|
problemDisplayId: schema.problem.displayId,
|
||||||
problemTitle: schema.problem.title,
|
problemTitle: schema.problem.title,
|
||||||
})
|
})
|
||||||
.from(schema.submission)
|
.from(schema.submission)
|
||||||
|
|||||||
@@ -627,7 +627,7 @@ submissionRoutes.get("/submissions", optionalAuth, async (c) => {
|
|||||||
) {
|
) {
|
||||||
return success(c, { results: [], total: 0 } satisfies SubmissionList)
|
return success(c, { results: [], total: 0 } satisfies SubmissionList)
|
||||||
}
|
}
|
||||||
const displayId = c.req.query("problemId")?.trim()
|
const displayId = c.req.query("problemDisplayId")?.trim()
|
||||||
const myself = c.req.query("myself") === "1" ? user : null
|
const myself = c.req.query("myself") === "1" ? user : null
|
||||||
// 「只看自己」盖过用户名
|
// 「只看自己」盖过用户名
|
||||||
const username = myself ? undefined : c.req.query("username")?.trim()
|
const username = myself ? undefined : c.req.query("username")?.trim()
|
||||||
@@ -673,7 +673,7 @@ submissionRoutes.get("/submissions", optionalAuth, async (c) => {
|
|||||||
({ submission, problem }) =>
|
({ submission, problem }) =>
|
||||||
({
|
({
|
||||||
id: submission.id,
|
id: submission.id,
|
||||||
problem: problem.displayId,
|
problemDisplayId: problem.displayId,
|
||||||
problemTitle: problem.title,
|
problemTitle: problem.title,
|
||||||
showLink: user
|
showLink: user
|
||||||
? canViewSubmission(user, submission, problem, null, joinTimes)
|
? canViewSubmission(user, submission, problem, null, joinTimes)
|
||||||
@@ -708,7 +708,7 @@ submissionRoutes.get(
|
|||||||
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })
|
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })
|
||||||
const offset = queryInteger(c.req.query("offset"), 0, { min: 0 })
|
const offset = queryInteger(c.req.query("offset"), 0, { min: 0 })
|
||||||
const user = c.get("user")
|
const user = c.get("user")
|
||||||
const displayId = c.req.query("problemId")?.trim()
|
const displayId = c.req.query("problemDisplayId")?.trim()
|
||||||
const myself = c.req.query("myself") === "1" ? user : null
|
const myself = c.req.query("myself") === "1" ? user : null
|
||||||
const username = myself ? undefined : c.req.query("username")?.trim()
|
const username = myself ? undefined : c.req.query("username")?.trim()
|
||||||
const result = c.req.query("result")
|
const result = c.req.query("result")
|
||||||
@@ -757,7 +757,7 @@ submissionRoutes.get(
|
|||||||
({ submission, problem }) =>
|
({ submission, problem }) =>
|
||||||
({
|
({
|
||||||
id: submission.id,
|
id: submission.id,
|
||||||
problem: problem.displayId,
|
problemDisplayId: problem.displayId,
|
||||||
problemTitle: problem.title,
|
problemTitle: problem.title,
|
||||||
showLink: user
|
showLink: user
|
||||||
? canViewSubmission(user, submission, problem, contest)
|
? canViewSubmission(user, submission, problem, contest)
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ export async function buildDetail(user: AuthUser, start: string, end: string) {
|
|||||||
const problemIds = firstAc.map((item) => item.problemId)
|
const problemIds = firstAc.map((item) => item.problemId)
|
||||||
if (!problemIds.length)
|
if (!problemIds.length)
|
||||||
return {
|
return {
|
||||||
user: user.username,
|
username: user.username,
|
||||||
className: user.className,
|
className: user.className,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
@@ -415,7 +415,7 @@ export async function buildDetail(user: AuthUser, start: string, end: string) {
|
|||||||
b.latestSubmissionTime.localeCompare(a.latestSubmissionTime),
|
b.latestSubmissionTime.localeCompare(a.latestSubmissionTime),
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
user: user.username,
|
username: user.username,
|
||||||
className: user.className,
|
className: user.className,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ async function viewSubmission(item: HelperItem) {
|
|||||||
// 查询该用户在该竞赛该题目的 AC 提交
|
// 查询该用户在该竞赛该题目的 AC 提交
|
||||||
const res = await getSubmissions({
|
const res = await getSubmissions({
|
||||||
username: item.username,
|
username: item.username,
|
||||||
problemId: item.problemDisplayId,
|
problemDisplayId: item.problemDisplayId,
|
||||||
contestId: props.contestID,
|
contestId: props.contestID,
|
||||||
result: "0", // ACCEPTED
|
result: "0", // ACCEPTED
|
||||||
language: "",
|
language: "",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ defineEmits<Emits>()
|
|||||||
</n-flex>
|
</n-flex>
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:columns="[
|
:columns="[
|
||||||
{ title: '题目ID', key: 'displayId', width: 80 },
|
{ title: '题目ID', key: 'problemDisplayId', width: 80 },
|
||||||
{ title: '题目标题', key: 'title', minWidth: 200 },
|
{ title: '题目标题', key: 'title', minWidth: 200 },
|
||||||
{ title: '顺序', key: 'order', width: 80 },
|
{ title: '顺序', key: 'order', width: 80 },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -161,15 +161,15 @@ watch(name, load)
|
|||||||
object-fit="contain"
|
object-fit="contain"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<n-text v-if="b.problemset" depth="3" class="source">
|
<n-text v-if="b.problemSet" depth="3" class="source">
|
||||||
来自题单
|
来自题单
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'problemset',
|
name: 'problemset',
|
||||||
params: { problemSetId: b.problemset.id },
|
params: { problemSetId: b.problemSet.id },
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{ b.problemset.title }}
|
{{ b.problemSet.title }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</n-text>
|
</n-text>
|
||||||
</n-thing>
|
</n-thing>
|
||||||
|
|||||||
@@ -174,20 +174,20 @@ export function adminRejudge(id: string) {
|
|||||||
export function getSubmissionStatisticsItems(
|
export function getSubmissionStatisticsItems(
|
||||||
duration: { start?: string; end: string },
|
duration: { start?: string; end: string },
|
||||||
username: string,
|
username: string,
|
||||||
problemID?: string,
|
problemDisplayId?: string,
|
||||||
) {
|
) {
|
||||||
return api.get<SubmissionStatisticsItems>("submissions/statistics/items", {
|
return api.get<SubmissionStatisticsItems>("submissions/statistics/items", {
|
||||||
params: { ...duration, problemId: problemID, username },
|
params: { ...duration, problemDisplayId, username },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubmissionStatistics(
|
export function getSubmissionStatistics(
|
||||||
duration: { start?: string; end: string },
|
duration: { start?: string; end: string },
|
||||||
problemID?: string,
|
problemDisplayId?: string,
|
||||||
username?: string,
|
username?: string,
|
||||||
) {
|
) {
|
||||||
return api.get<SubmissionStatistics>("submissions/statistics", {
|
return api.get<SubmissionStatistics>("submissions/statistics", {
|
||||||
params: { ...duration, problemId: problemID, username },
|
params: { ...duration, problemDisplayId, username },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,7 +410,7 @@ export function getFlowchartSubmission(id: string) {
|
|||||||
|
|
||||||
export function getFlowchartSubmissions(params: {
|
export function getFlowchartSubmissions(params: {
|
||||||
username?: string
|
username?: string
|
||||||
problemId?: string
|
problemDisplayId?: string
|
||||||
myself?: string
|
myself?: string
|
||||||
offset?: number
|
offset?: number
|
||||||
limit?: number
|
limit?: number
|
||||||
@@ -422,11 +422,11 @@ export function getFlowchartSubmissions(params: {
|
|||||||
|
|
||||||
export function getFlowchartStatistics(
|
export function getFlowchartStatistics(
|
||||||
duration: { start?: string; end: string },
|
duration: { start?: string; end: string },
|
||||||
problemID?: string,
|
problemDisplayId?: string,
|
||||||
username?: string,
|
username?: string,
|
||||||
) {
|
) {
|
||||||
return api.get<FlowchartStatistics>("flowcharts/statistics", {
|
return api.get<FlowchartStatistics>("flowcharts/statistics", {
|
||||||
params: { ...duration, problemId: problemID, username },
|
params: { ...duration, problemDisplayId, username },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ async function listSubmissions() {
|
|||||||
...query,
|
...query,
|
||||||
myself: "1",
|
myself: "1",
|
||||||
offset,
|
offset,
|
||||||
problemId: (route.params.problemID as string) ?? "",
|
problemDisplayId: (route.params.problemID as string) ?? "",
|
||||||
contestId: (route.params.contestID as string) ?? "",
|
contestId: (route.params.contestID as string) ?? "",
|
||||||
})
|
})
|
||||||
submissions.value = res.results
|
submissions.value = res.results
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ async function loadUserBadges() {
|
|||||||
|
|
||||||
const res = await getUserBadges()
|
const res = await getUserBadges()
|
||||||
userBadges.value = res.filter(
|
userBadges.value = res.filter(
|
||||||
(badge: UserBadgeType) => badge.badge.problemsetId === problemSetId.value,
|
(badge: UserBadgeType) => badge.badge.problemSetId === problemSetId.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const useAIStore = defineStore("ai", () => {
|
|||||||
const rangeEnd = ref("")
|
const rangeEnd = ref("")
|
||||||
const durationData = ref<DurationData[]>([])
|
const durationData = ref<DurationData[]>([])
|
||||||
const detailsData = reactive<DetailsData>({
|
const detailsData = reactive<DetailsData>({
|
||||||
user: "",
|
username: "",
|
||||||
start: "",
|
start: "",
|
||||||
end: "",
|
end: "",
|
||||||
grade: "",
|
grade: "",
|
||||||
|
|||||||
@@ -148,14 +148,18 @@ onMounted(async () => {
|
|||||||
<template v-if="stats.problems.length">
|
<template v-if="stats.problems.length">
|
||||||
<n-divider style="margin: 16px 0">今天最热的题</n-divider>
|
<n-divider style="margin: 16px 0">今天最热的题</n-divider>
|
||||||
<div class="rows">
|
<div class="rows">
|
||||||
<div class="row" v-for="row in stats.problems" :key="row.problem">
|
<div
|
||||||
|
class="row"
|
||||||
|
v-for="row in stats.problems"
|
||||||
|
:key="row.problemDisplayId"
|
||||||
|
>
|
||||||
<n-button
|
<n-button
|
||||||
class="problem"
|
class="problem"
|
||||||
text
|
text
|
||||||
type="info"
|
type="info"
|
||||||
@click="emit('openProblem', row.problem)"
|
@click="emit('openProblem', row.problemDisplayId)"
|
||||||
>
|
>
|
||||||
{{ row.problem }} {{ row.problemTitle }}
|
{{ row.problemDisplayId }} {{ row.problemTitle }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-text class="row-count" depth="3">
|
<n-text class="row-count" depth="3">
|
||||||
{{ row.count }} 条 / 正确 {{ row.acceptedCount }}
|
{{ row.count }} 条 / 正确 {{ row.acceptedCount }}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ async function listSubmissions() {
|
|||||||
if (query.language === "Flowchart") {
|
if (query.language === "Flowchart") {
|
||||||
const res = await getFlowchartSubmissions({
|
const res = await getFlowchartSubmissions({
|
||||||
username: query.username,
|
username: query.username,
|
||||||
problemId: query.problem,
|
problemDisplayId: query.problem,
|
||||||
myself: query.myself,
|
myself: query.myself,
|
||||||
offset,
|
offset,
|
||||||
limit: query.limit,
|
limit: query.limit,
|
||||||
@@ -173,7 +173,7 @@ async function listSubmissions() {
|
|||||||
const res = await getSubmissions({
|
const res = await getSubmissions({
|
||||||
...query,
|
...query,
|
||||||
offset,
|
offset,
|
||||||
problemId: query.problem,
|
problemDisplayId: query.problem,
|
||||||
contestId: (route.params.contestID as string) ?? "",
|
contestId: (route.params.contestID as string) ?? "",
|
||||||
language: query.language,
|
language: query.language,
|
||||||
today: query.today,
|
today: query.today,
|
||||||
@@ -239,12 +239,12 @@ function problemClicked(row: SubmissionListItem | FlowchartSubmissionListItem) {
|
|||||||
const path = router.resolve({
|
const path = router.resolve({
|
||||||
name: "contest problem",
|
name: "contest problem",
|
||||||
params: {
|
params: {
|
||||||
problemID: row.problem,
|
problemID: row.problemDisplayId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
window.open(path.href, "_blank")
|
window.open(path.href, "_blank")
|
||||||
} else {
|
} else {
|
||||||
window.open("/problem/" + row.problem, "_blank")
|
window.open("/problem/" + row.problemDisplayId, "_blank")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ function consumePendingJump() {
|
|||||||
message.info("这一页没有可以查看的代码")
|
message.info("这一页没有可以查看的代码")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
showCodePanel(row.id, row.problem)
|
showCodePanel(row.id, row.problemDisplayId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveCodePanel(step: 1 | -1) {
|
function moveCodePanel(step: 1 | -1) {
|
||||||
@@ -297,7 +297,7 @@ function moveCodePanel(step: 1 | -1) {
|
|||||||
if (index === -1) return
|
if (index === -1) return
|
||||||
const next = viewableSubmissions.value[index + step]
|
const next = viewableSubmissions.value[index + step]
|
||||||
if (next) {
|
if (next) {
|
||||||
showCodePanel(next.id, next.problem)
|
showCodePanel(next.id, next.problemDisplayId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const page = query.page + step
|
const page = query.page + step
|
||||||
@@ -390,7 +390,7 @@ const columns = computed(() => {
|
|||||||
render: (row) =>
|
render: (row) =>
|
||||||
h(SubmissionLink, {
|
h(SubmissionLink, {
|
||||||
submission: row,
|
submission: row,
|
||||||
onShowCode: () => showCodePanel(row.id, row.problem),
|
onShowCode: () => showCodePanel(row.id, row.problemDisplayId),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -409,9 +409,9 @@ const columns = computed(() => {
|
|||||||
{
|
{
|
||||||
type: "题目",
|
type: "题目",
|
||||||
onClick: () => problemClicked(row),
|
onClick: () => problemClicked(row),
|
||||||
onSearch: () => (query.problem = row.problem),
|
onSearch: () => (query.problem = row.problemDisplayId),
|
||||||
},
|
},
|
||||||
() => `${row.problem} ${row.problemTitle}`,
|
() => `${row.problemDisplayId} ${row.problemTitle}`,
|
||||||
)
|
)
|
||||||
// 从题单入口做出来的提交才有这个标记(后端只在提交时记来源),
|
// 从题单入口做出来的提交才有这个标记(后端只在提交时记来源),
|
||||||
// 同一道题从普通题库刷的不会带。老提交里只有当年首次 AC 那条有
|
// 同一道题从普通题库刷的不会带。老提交里只有当年首次 AC 那条有
|
||||||
@@ -507,9 +507,9 @@ const flowchartColumns = computed(() => {
|
|||||||
{
|
{
|
||||||
type: "题目",
|
type: "题目",
|
||||||
onClick: () => problemClicked(row),
|
onClick: () => problemClicked(row),
|
||||||
onSearch: () => (query.problem = row.problem),
|
onSearch: () => (query.problem = row.problemDisplayId),
|
||||||
},
|
},
|
||||||
() => `${row.problem} ${row.problemTitle}`,
|
() => `${row.problemDisplayId} ${row.problemTitle}`,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
<n-button
|
<n-button
|
||||||
text
|
text
|
||||||
type="info"
|
type="info"
|
||||||
@click="router.push('/problem/' + item.submission.problem)"
|
@click="router.push('/problem/' + item.submission.problemDisplayId)"
|
||||||
>
|
>
|
||||||
{{ item.submission.problem }}
|
{{ item.submission.problemDisplayId }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-text :type="JUDGE_STATUS[item.submission.result]['type']">
|
<n-text :type="JUDGE_STATUS[item.submission.result]['type']">
|
||||||
{{ JUDGE_STATUS[item.submission.result]["name"] }}
|
{{ JUDGE_STATUS[item.submission.result]["name"] }}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<n-flex vertical size="small">
|
<n-flex vertical size="small">
|
||||||
<n-text depth="3">
|
<n-text depth="3">
|
||||||
最近一次:{{ item.failure?.problem }} ·
|
最近一次:{{ item.failure?.problemDisplayId }} ·
|
||||||
{{ statusName(item.failure?.result) }}
|
{{ statusName(item.failure?.result) }}
|
||||||
</n-text>
|
</n-text>
|
||||||
<pre v-if="item.failure?.error" class="failure-error">{{
|
<pre v-if="item.failure?.error" class="failure-error">{{
|
||||||
@@ -252,17 +252,17 @@ function groupByProblem(list: SubmissionStatisticsItems["items"]) {
|
|||||||
const groups = new Map<
|
const groups = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
problem: string
|
problemDisplayId: string
|
||||||
problemTitle: string
|
problemTitle: string
|
||||||
items: SubmissionStatisticsItems["items"]
|
items: SubmissionStatisticsItems["items"]
|
||||||
}
|
}
|
||||||
>()
|
>()
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
const group = groups.get(item.problem)
|
const group = groups.get(item.problemDisplayId)
|
||||||
if (group) group.items.push(item)
|
if (group) group.items.push(item)
|
||||||
else
|
else
|
||||||
groups.set(item.problem, {
|
groups.set(item.problemDisplayId, {
|
||||||
problem: item.problem,
|
problemDisplayId: item.problemDisplayId,
|
||||||
problemTitle: item.problemTitle,
|
problemTitle: item.problemTitle,
|
||||||
items: [item],
|
items: [item],
|
||||||
})
|
})
|
||||||
@@ -313,7 +313,7 @@ const columns: DataTableColumn<SubmissionStatisticsUser>[] = [
|
|||||||
h(
|
h(
|
||||||
NTag,
|
NTag,
|
||||||
{ size: "small", bordered: false },
|
{ size: "small", bordered: false },
|
||||||
() => group.problem,
|
() => group.problemDisplayId,
|
||||||
),
|
),
|
||||||
h(
|
h(
|
||||||
NText,
|
NText,
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ export interface SubmissionListPayload {
|
|||||||
result?: string
|
result?: string
|
||||||
username?: string
|
username?: string
|
||||||
contestId?: string
|
contestId?: string
|
||||||
problemId?: string
|
problemDisplayId?: string
|
||||||
language: LANGUAGE | ""
|
language: LANGUAGE | ""
|
||||||
today?: "1" | "0"
|
today?: "1" | "0"
|
||||||
page: number
|
page: number
|
||||||
|
|||||||
@@ -421,9 +421,9 @@ export const updateProblemSetStatusRequestSchema = z.object({
|
|||||||
|
|
||||||
export const adminProblemSetProblemSchema = z.object({
|
export const adminProblemSetProblemSchema = z.object({
|
||||||
id: z.number().int(),
|
id: z.number().int(),
|
||||||
problemsetId: z.number().int(),
|
problemSetId: z.number().int(),
|
||||||
problemId: z.number().int(),
|
problemId: z.number().int(),
|
||||||
displayId: z.string(),
|
problemDisplayId: z.string(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
difficulty: z.string(),
|
difficulty: z.string(),
|
||||||
order: z.number().int(),
|
order: z.number().int(),
|
||||||
@@ -450,7 +450,7 @@ export const updateProblemInSetRequestSchema = z.object({
|
|||||||
|
|
||||||
export const adminProblemSetBadgeSchema = z.object({
|
export const adminProblemSetBadgeSchema = z.object({
|
||||||
id: z.number().int(),
|
id: z.number().int(),
|
||||||
problemsetId: z.number().int(),
|
problemSetId: z.number().int(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
icon: z.string(),
|
icon: z.string(),
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const activityBucketSchema = z.object({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const aiDetailSchema = z.object({
|
export const aiDetailSchema = z.object({
|
||||||
user: z.string(),
|
username: z.string(),
|
||||||
className: z.string().nullable(),
|
className: z.string().nullable(),
|
||||||
start: z.string(),
|
start: z.string(),
|
||||||
end: z.string(),
|
end: z.string(),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const flowchartSubmissionSchema = z.object({
|
|||||||
export const flowchartListItemSchema = z.object({
|
export const flowchartListItemSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
problem: z.string(),
|
problemDisplayId: z.string(),
|
||||||
problemTitle: z.string(),
|
problemTitle: z.string(),
|
||||||
status: flowchartStatusSchema,
|
status: flowchartStatusSchema,
|
||||||
createTime: z.string(),
|
createTime: z.string(),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const problemSetUserProgressSummarySchema = z.object({
|
|||||||
|
|
||||||
export const problemSetBadgeSchema = z.object({
|
export const problemSetBadgeSchema = z.object({
|
||||||
id: z.number().int(),
|
id: z.number().int(),
|
||||||
problemsetId: z.number().int(),
|
problemSetId: z.number().int(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
icon: z.string(),
|
icon: z.string(),
|
||||||
@@ -58,7 +58,7 @@ export const problemSetProblemItemSchema = z.object({
|
|||||||
|
|
||||||
export const problemSetProblemSchema = z.object({
|
export const problemSetProblemSchema = z.object({
|
||||||
id: z.number().int(),
|
id: z.number().int(),
|
||||||
problemsetId: z.number().int(),
|
problemSetId: z.number().int(),
|
||||||
problem: problemSetProblemItemSchema,
|
problem: problemSetProblemItemSchema,
|
||||||
order: z.number().int(),
|
order: z.number().int(),
|
||||||
isRequired: z.boolean(),
|
isRequired: z.boolean(),
|
||||||
@@ -79,7 +79,7 @@ export const completedProblemSchema = z.object({
|
|||||||
|
|
||||||
export const problemSetProgressSchema = z.object({
|
export const problemSetProgressSchema = z.object({
|
||||||
id: z.number().int(),
|
id: z.number().int(),
|
||||||
problemsetId: z.number().int(),
|
problemSetId: z.number().int(),
|
||||||
user: sampleUserSchema,
|
user: sampleUserSchema,
|
||||||
joinTime: z.string(),
|
joinTime: z.string(),
|
||||||
completeTime: z.string().nullable(),
|
completeTime: z.string().nullable(),
|
||||||
@@ -107,7 +107,7 @@ export const userBadgeSchema = z.object({
|
|||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
badge: problemSetBadgeSchema,
|
badge: problemSetBadgeSchema,
|
||||||
earnedTime: z.string(),
|
earnedTime: z.string(),
|
||||||
problemset: z.object({ id: z.number().int(), title: z.string() }),
|
problemSet: z.object({ id: z.number().int(), title: z.string() }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type ProblemSet = z.infer<typeof problemSetSchema>
|
export type ProblemSet = z.infer<typeof problemSetSchema>
|
||||||
|
|||||||
@@ -191,20 +191,14 @@ export const submissionDetailSchema = z.object({
|
|||||||
* 将来有人「顺手」把空值改成真值就不会变成泄露,因为这里压根没有这些字段。
|
* 将来有人「顺手」把空值改成真值就不会变成泄露,因为这里压根没有这些字段。
|
||||||
*/
|
*/
|
||||||
export const embeddedSubmissionSchema = submissionDetailSchema
|
export const embeddedSubmissionSchema = submissionDetailSchema
|
||||||
// problemDisplayId 也要去掉:下面的 problem 就是它,同一个值留两份,
|
// 留下 problemDisplayId(展示用题号)而不是数字主键:站内信页面拿它拼
|
||||||
// 而路由只填了 problem —— 这里漏 omit 的那阵子,凡是收到过站内信的人
|
// `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
|
||||||
// 打开消息页都是 500(parse 抛在缺失的 problemDisplayId 上,列表为空时才碰巧不炸)。
|
|
||||||
.omit({
|
.omit({
|
||||||
info: true,
|
info: true,
|
||||||
contestId: true,
|
contestId: true,
|
||||||
problemId: true,
|
problemId: true,
|
||||||
problemDisplayId: true,
|
|
||||||
caseSummary: true,
|
caseSummary: true,
|
||||||
})
|
})
|
||||||
// 旧 SubmissionSafeModelSerializer 里 problem 是
|
|
||||||
// `SlugRelatedField(slug_field="_id")`,即**展示用题号**而非数字主键。
|
|
||||||
// 站内信页面拿它拼 `/problem/<题号>` 链接,给数字 id 会拼出打不开的地址。
|
|
||||||
.extend({ problem: z.string() })
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判题进度推送。**只带前端真正要用的东西**:靠 submissionId 认领、靠 result /
|
* 判题进度推送。**只带前端真正要用的东西**:靠 submissionId 认领、靠 result /
|
||||||
@@ -224,7 +218,7 @@ export const submissionUpdateSchema = z.object({
|
|||||||
|
|
||||||
export const submissionListItemSchema = z.object({
|
export const submissionListItemSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
problem: z.string(),
|
problemDisplayId: z.string(),
|
||||||
problemTitle: z.string(),
|
problemTitle: z.string(),
|
||||||
showLink: z.boolean(),
|
showLink: z.boolean(),
|
||||||
createTime: z.string(),
|
createTime: z.string(),
|
||||||
@@ -277,7 +271,7 @@ export const attemptedStudentSchema = unacceptedStudentSchema.extend({
|
|||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
/** 题目的展示编号,用来告诉老师错在哪道题 */
|
/** 题目的展示编号,用来告诉老师错在哪道题 */
|
||||||
problem: z.string(),
|
problemDisplayId: z.string(),
|
||||||
result: judgeStatusSchema,
|
result: judgeStatusSchema,
|
||||||
error: z.string().nullable(),
|
error: z.string().nullable(),
|
||||||
})
|
})
|
||||||
@@ -325,7 +319,7 @@ export const submissionStatisticsItemsSchema = z.object({
|
|||||||
* 展开某个学生时列出他这段时间的提交。**带上题目**:一节课里学生往往在好几道题
|
* 展开某个学生时列出他这段时间的提交。**带上题目**:一节课里学生往往在好几道题
|
||||||
* 之间来回跳,一串只有编号的按钮看不出他卡在哪一道 —— 前端按题目分组展示。
|
* 之间来回跳,一串只有编号的按钮看不出他卡在哪一道 —— 前端按题目分组展示。
|
||||||
*
|
*
|
||||||
* 字段名沿用 submissionListItemSchema 的口径:`problem` 是展示用题号(problem._id),
|
* 字段名沿用 submissionListItemSchema 的口径:`problemDisplayId` 是展示用题号(problem._id),
|
||||||
* `problemTitle` 是标题。
|
* `problemTitle` 是标题。
|
||||||
*/
|
*/
|
||||||
items: z.array(
|
items: z.array(
|
||||||
@@ -333,7 +327,7 @@ export const submissionStatisticsItemsSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
result: judgeStatusSchema,
|
result: judgeStatusSchema,
|
||||||
createTime: z.string(),
|
createTime: z.string(),
|
||||||
problem: z.string(),
|
problemDisplayId: z.string(),
|
||||||
problemTitle: z.string(),
|
problemTitle: z.string(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -405,7 +399,7 @@ export const todaySubmissionStatisticsSchema = z.object({
|
|||||||
*/
|
*/
|
||||||
problems: z.array(
|
problems: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
problem: z.string(),
|
problemDisplayId: z.string(),
|
||||||
problemTitle: z.string(),
|
problemTitle: z.string(),
|
||||||
count: z.number().int(),
|
count: z.number().int(),
|
||||||
acceptedCount: z.number().int(),
|
acceptedCount: z.number().int(),
|
||||||
|
|||||||
Reference in new issue
Block a user