refactor(契约): 展示题号统一叫 problemDisplayId,题单 id 统一叫 problemSetId
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:
xuyueandClaude Opus 5.5 committed 2026-09-27 16:08:31 +08:00
1 parent 5ae830a7f9
commit 9412ba3cf6
24 files changed
+80 -82

No files matched your search

+1 -1
View File
@@ -134,7 +134,7 @@ async function viewSubmission(item: HelperItem) {
// 查询该用户在该竞赛该题目的 AC 提交
const res = await getSubmissions({
username: item.username,
problemId: item.problemDisplayId,
problemDisplayId: item.problemDisplayId,
contestId: props.contestID,
result: "0", // ACCEPTED
language: "",
@@ -27,7 +27,7 @@ defineEmits<Emits>()
</n-flex>
<n-data-table
:columns="[
{ title: '题目ID', key: 'displayId', width: 80 },
{ title: '题目ID', key: 'problemDisplayId', width: 80 },
{ title: '题目标题', key: 'title', minWidth: 200 },
{ title: '顺序', key: 'order', width: 80 },
{
+3 -3
View File
@@ -161,15 +161,15 @@ watch(name, load)
object-fit="contain"
/>
</template>
<n-text v-if="b.problemset" depth="3" class="source">
<n-text v-if="b.problemSet" depth="3" class="source">
来自题单
<router-link
:to="{
name: 'problemset',
params: { problemSetId: b.problemset.id },
params: { problemSetId: b.problemSet.id },
}"
>
{{ b.problemset.title }}
{{ b.problemSet.title }}
</router-link>
</n-text>
</n-thing>
+7 -7
View File
@@ -174,20 +174,20 @@ export function adminRejudge(id: string) {
export function getSubmissionStatisticsItems(
duration: { start?: string; end: string },
username: string,
problemID?: string,
problemDisplayId?: string,
) {
return api.get<SubmissionStatisticsItems>("submissions/statistics/items", {
params: { ...duration, problemId: problemID, username },
params: { ...duration, problemDisplayId, username },
})
}
export function getSubmissionStatistics(
duration: { start?: string; end: string },
problemID?: string,
problemDisplayId?: string,
username?: string,
) {
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: {
username?: string
problemId?: string
problemDisplayId?: string
myself?: string
offset?: number
limit?: number
@@ -422,11 +422,11 @@ export function getFlowchartSubmissions(params: {
export function getFlowchartStatistics(
duration: { start?: string; end: string },
problemID?: string,
problemDisplayId?: string,
username?: string,
) {
return api.get<FlowchartStatistics>("flowcharts/statistics", {
params: { ...duration, problemId: problemID, username },
params: { ...duration, problemDisplayId, username },
})
}
@@ -126,7 +126,7 @@ async function listSubmissions() {
...query,
myself: "1",
offset,
problemId: (route.params.problemID as string) ?? "",
problemDisplayId: (route.params.problemID as string) ?? "",
contestId: (route.params.contestID as string) ?? "",
})
submissions.value = res.results
+1 -1
View File
@@ -49,7 +49,7 @@ async function loadUserBadges() {
const res = await getUserBadges()
userBadges.value = res.filter(
(badge: UserBadgeType) => badge.badge.problemsetId === problemSetId.value,
(badge: UserBadgeType) => badge.badge.problemSetId === problemSetId.value,
)
}
+1 -1
View File
@@ -16,7 +16,7 @@ export const useAIStore = defineStore("ai", () => {
const rangeEnd = ref("")
const durationData = ref<DurationData[]>([])
const detailsData = reactive<DetailsData>({
user: "",
username: "",
start: "",
end: "",
grade: "",
@@ -148,14 +148,18 @@ onMounted(async () => {
<template v-if="stats.problems.length">
<n-divider style="margin: 16px 0">今天最热的题</n-divider>
<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
class="problem"
text
type="info"
@click="emit('openProblem', row.problem)"
@click="emit('openProblem', row.problemDisplayId)"
>
{{ row.problem }} {{ row.problemTitle }}
{{ row.problemDisplayId }} {{ row.problemTitle }}
</n-button>
<n-text class="row-count" depth="3">
{{ row.count }} 条 / 正确 {{ row.acceptedCount }}
+11 -11
View File
@@ -160,7 +160,7 @@ async function listSubmissions() {
if (query.language === "Flowchart") {
const res = await getFlowchartSubmissions({
username: query.username,
problemId: query.problem,
problemDisplayId: query.problem,
myself: query.myself,
offset,
limit: query.limit,
@@ -173,7 +173,7 @@ async function listSubmissions() {
const res = await getSubmissions({
...query,
offset,
problemId: query.problem,
problemDisplayId: query.problem,
contestId: (route.params.contestID as string) ?? "",
language: query.language,
today: query.today,
@@ -239,12 +239,12 @@ function problemClicked(row: SubmissionListItem | FlowchartSubmissionListItem) {
const path = router.resolve({
name: "contest problem",
params: {
problemID: row.problem,
problemID: row.problemDisplayId,
},
})
window.open(path.href, "_blank")
} else {
window.open("/problem/" + row.problem, "_blank")
window.open("/problem/" + row.problemDisplayId, "_blank")
}
}
@@ -289,7 +289,7 @@ function consumePendingJump() {
message.info("这一页没有可以查看的代码")
return
}
showCodePanel(row.id, row.problem)
showCodePanel(row.id, row.problemDisplayId)
}
function moveCodePanel(step: 1 | -1) {
@@ -297,7 +297,7 @@ function moveCodePanel(step: 1 | -1) {
if (index === -1) return
const next = viewableSubmissions.value[index + step]
if (next) {
showCodePanel(next.id, next.problem)
showCodePanel(next.id, next.problemDisplayId)
return
}
const page = query.page + step
@@ -390,7 +390,7 @@ const columns = computed(() => {
render: (row) =>
h(SubmissionLink, {
submission: row,
onShowCode: () => showCodePanel(row.id, row.problem),
onShowCode: () => showCodePanel(row.id, row.problemDisplayId),
}),
},
{
@@ -409,9 +409,9 @@ const columns = computed(() => {
{
type: "题目",
onClick: () => problemClicked(row),
onSearch: () => (query.problem = row.problem),
onSearch: () => (query.problem = row.problemDisplayId),
},
() => `${row.problem} ${row.problemTitle}`,
() => `${row.problemDisplayId} ${row.problemTitle}`,
)
// 从题单入口做出来的提交才有这个标记(后端只在提交时记来源),
// 同一道题从普通题库刷的不会带。老提交里只有当年首次 AC 那条有
@@ -507,9 +507,9 @@ const flowchartColumns = computed(() => {
{
type: "题目",
onClick: () => problemClicked(row),
onSearch: () => (query.problem = row.problem),
onSearch: () => (query.problem = row.problemDisplayId),
},
() => `${row.problem} ${row.problemTitle}`,
() => `${row.problemDisplayId} ${row.problemTitle}`,
),
},
{
+2 -2
View File
@@ -17,9 +17,9 @@
<n-button
text
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-text :type="JUDGE_STATUS[item.submission.result]['type']">
{{ JUDGE_STATUS[item.submission.result]["name"] }}
@@ -172,7 +172,7 @@
</template>
<n-flex vertical size="small">
<n-text depth="3">
最近一次:{{ item.failure?.problem }} ·
最近一次:{{ item.failure?.problemDisplayId }} ·
{{ statusName(item.failure?.result) }}
</n-text>
<pre v-if="item.failure?.error" class="failure-error">{{
@@ -252,17 +252,17 @@ function groupByProblem(list: SubmissionStatisticsItems["items"]) {
const groups = new Map<
string,
{
problem: string
problemDisplayId: string
problemTitle: string
items: SubmissionStatisticsItems["items"]
}
>()
for (const item of list) {
const group = groups.get(item.problem)
const group = groups.get(item.problemDisplayId)
if (group) group.items.push(item)
else
groups.set(item.problem, {
problem: item.problem,
groups.set(item.problemDisplayId, {
problemDisplayId: item.problemDisplayId,
problemTitle: item.problemTitle,
items: [item],
})
@@ -313,7 +313,7 @@ const columns: DataTableColumn<SubmissionStatisticsUser>[] = [
h(
NTag,
{ size: "small", bordered: false },
() => group.problem,
() => group.problemDisplayId,
),
h(
NText,
+1 -1
View File
@@ -275,7 +275,7 @@ export interface SubmissionListPayload {
result?: string
username?: string
contestId?: string
problemId?: string
problemDisplayId?: string
language: LANGUAGE | ""
today?: "1" | "0"
page: number