From ad4bbba04869b785c5cd54921661748abb997b12 Mon Sep 17 00:00:00 2001 From: Junyoung Yang Date: Thu, 25 Jun 2026 17:30:42 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=EC=A0=9C=EC=B6=9C=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/submission/serializers.py | 22 +++++++++++++++++++++- backend/submission/tests.py | 18 ++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/backend/submission/serializers.py b/backend/submission/serializers.py index 74ab7d24b..e40a8e1f8 100644 --- a/backend/submission/serializers.py +++ b/backend/submission/serializers.py @@ -1,4 +1,4 @@ -from .models import Submission +from .models import JudgeStatus, Submission from utils.api import serializers from utils.serializers import LanguageNameChoiceField @@ -34,6 +34,8 @@ class Meta: class SubmissionListSerializer(serializers.ModelSerializer): problem = serializers.SlugRelatedField(read_only=True, slug_field="_id") + problem_id = serializers.SerializerMethodField() + result_display = serializers.SerializerMethodField() show_link = serializers.SerializerMethodField() user_avatar = serializers.SerializerMethodField() @@ -53,3 +55,21 @@ def get_show_link(self, obj): def get_user_avatar(self, obj): return obj.user_avatar + + def get_problem_id(self, obj): + return obj.problem._id + + def get_result_display(self, obj): + return { + JudgeStatus.COMPILE_ERROR: "컴파일 에러", + JudgeStatus.WRONG_ANSWER: "오답", + JudgeStatus.ACCEPTED: "정답", + JudgeStatus.CPU_TIME_LIMIT_EXCEEDED: "시간 초과", + JudgeStatus.REAL_TIME_LIMIT_EXCEEDED: "시간 초과", + JudgeStatus.MEMORY_LIMIT_EXCEEDED: "메모리 초과", + JudgeStatus.RUNTIME_ERROR: "런타임 에러", + JudgeStatus.SYSTEM_ERROR: "시스템 에러", + JudgeStatus.PENDING: "대기 중", + JudgeStatus.JUDGING: "채점 중", + JudgeStatus.PARTIALLY_ACCEPTED: "부분 정답", + }.get(obj.result, obj.result) diff --git a/backend/submission/tests.py b/backend/submission/tests.py index d32cf37ba..8fac2b7ba 100644 --- a/backend/submission/tests.py +++ b/backend/submission/tests.py @@ -232,6 +232,11 @@ def test_get_submissions_general(self): resp = self.client.get(f"{self.url}?limit=10") self.assertSuccess(resp) self.assertTrue("results" in resp.data["data"]) + result = next( + sub for sub in resp.data["data"]["results"] if sub["id"] == submission.id + ) + self.assertEqual(result["problem_id"], self.problem._id) + self.assertEqual(result["result_display"], "대기 중") def test_get_submissions_filtered_by_problem(self): # 문제 필터링 테스트 (일반 문제만) @@ -281,10 +286,19 @@ def test_get_submissions_contest_admin_access(self): def test_get_submissions_contest_myself(self): # contest_id 지정, myself=1일때는 조회 성공 - sub = self.create_submission(self.user, self.contest_problem, contest_id=self.contest["id"]) + sub = self.create_submission( + self.user, + self.contest_problem, + contest_id=self.contest["id"], + result=JudgeStatus.ACCEPTED, + ) resp = self.client.get(f"{self.url}?limit=10&contest_id={self.contest['id']}&myself=1") self.assertSuccess(resp) - self.assertTrue(any(s["id"] == sub.id for s in resp.data["data"]["results"])) + result = next( + s for s in resp.data["data"]["results"] if s["id"] == sub.id + ) + self.assertEqual(result["problem_id"], self.contest_problem._id) + self.assertEqual(result["result_display"], "정답") def test_get_submissions_with_problem_not_exist(self): # problem_id 지정했으나 존재하지 않는 경우 에러 발생 From 9f36b8af057be83f67afec18a1a2fe2d3ceb9b33 Mon Sep 17 00:00:00 2001 From: Junyoung Yang Date: Thu, 25 Jun 2026 17:31:03 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=EC=B1=84=EC=A0=90=ED=98=84=ED=99=A9=20UI?= =?UTF-8?q?=20=EA=B0=9C=ED=8E=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/i18n/oj/en-US.js | 29 +- frontend/src/i18n/oj/zh-CN.js | 1 + frontend/src/i18n/oj/zh-TW.js | 1 + frontend/src/pages/oj/App.vue | 85 +- .../oj/views/submission/SubmissionList.vue | 758 +++++++++++------- .../views/submission/SubmissionListItem.vue | 314 ++++++-- .../oj/views/submission/SubmissionTable.vue | 220 ++++- 7 files changed, 1019 insertions(+), 389 deletions(-) diff --git a/frontend/src/i18n/oj/en-US.js b/frontend/src/i18n/oj/en-US.js index 8c0c00968..4fa83e0c1 100644 --- a/frontend/src/i18n/oj/en-US.js +++ b/frontend/src/i18n/oj/en-US.js @@ -30,9 +30,9 @@ export const m = { Tags: "태그", Show: "Show", Submit: "제출", - Submitting: "처리중", - Judging: "Judging", - Wrong_Answer: "Wrong Answer", + Submitting: "제출 중", + Judging: "채점 중", + Wrong_Answer: "오답", Statistic: "Statistic", Close: "Close", View_Contest: "View Contest", @@ -380,21 +380,24 @@ export const m = { // SubmissionList.vue When: "제출시각", ID: "ID", - Time: "Time", + Time: "시간", Memory: "메모리", Author: "작성자", + Problem_ID: "문제 번호", Option: "옵션", Check: "확인", - Mine: "사용자", + Mine: "내 제출만", Submission_Table_Author: "제출자", - Accepted: "Accepted", - Time_Limit_Exceeded: "Time Limit Exceeded", - Memory_Limit_Exceeded: "Memory Limit Exceeded", - Runtime_Error: "Runtime Error", - System_Error: "System Error", - Pending: "Pending", - Partial_Accepted: "Partial Accepted", - Compile_Error: "Compile Error", + Search_Submitter: "제출자 검색", + Accepted: "정답", + Time_Limit_Exceeded: "시간 초과", + Memory_Limit_Exceeded: "메모리 초과", + Runtime_Error: "런타임 에러", + System_Error: "시스템 에러", + Pending: "대기 중", + Partial_Accepted: "부분 정답", + Compile_Error: "컴파일 에러", + Loading: "불러오는 중...", Rejudge: "재채점", Rejudging: "채점중...", diff --git a/frontend/src/i18n/oj/zh-CN.js b/frontend/src/i18n/oj/zh-CN.js index 7220c4b78..15b9fecb4 100644 --- a/frontend/src/i18n/oj/zh-CN.js +++ b/frontend/src/i18n/oj/zh-CN.js @@ -254,6 +254,7 @@ export const m = { Option: "选项", Mine: "我的", Search_Author: "搜索作者", + Search_Submitter: "搜索提交者", Accepted: "答案正确", Time_Limit_Exceeded: "运行超时", Memory_Limit_Exceeded: "内存超限", diff --git a/frontend/src/i18n/oj/zh-TW.js b/frontend/src/i18n/oj/zh-TW.js index 38318a58f..53847edaa 100644 --- a/frontend/src/i18n/oj/zh-TW.js +++ b/frontend/src/i18n/oj/zh-TW.js @@ -255,6 +255,7 @@ export const m = { Option: "選項", Mine: "我的", Search_Author: "搜尋作者", + Search_Submitter: "搜尋提交者", Accepted: "答案正確", Time_Limit_Exceeded: "超出時間限制", Memory_Limit_Exceeded: "超出記憶體空間限制", diff --git a/frontend/src/pages/oj/App.vue b/frontend/src/pages/oj/App.vue index 21fa849fc..4d4c160c9 100644 --- a/frontend/src/pages/oj/App.vue +++ b/frontend/src/pages/oj/App.vue @@ -6,13 +6,19 @@ -
+
@@ -41,20 +47,47 @@ export default { }, mounted() { this.getWebsiteConfig() + this.syncSubmissionListChrome() + }, + updated() { + this.syncSubmissionListChrome() + }, + beforeDestroy() { + this.syncSubmissionListChrome(false) }, methods: { ...mapActions(["getWebsiteConfig", "changeDomTitle"]), + syncSubmissionListChrome(force) { + const enabled = + typeof force === "boolean" ? force : this.isSubmissionList + document.documentElement.classList.toggle( + "submission-list-page", + enabled, + ) + document.body.classList.toggle("submission-list-page", enabled) + }, }, computed: { ...mapState(["website"]), ...mapGetters(["isProblemSolving", "removedPopupId"]), + isSubmissionList() { + return ( + this.$route.name === "submission-list" || this.$route.path === "/status" + ) + }, }, watch: { website() { this.changeDomTitle() }, - $route() { - this.changeDomTitle() + $route: { + immediate: true, + handler() { + this.changeDomTitle() + this.$nextTick(() => { + this.syncSubmissionListChrome() + }) + }, }, }, } @@ -253,6 +286,50 @@ a { background-color: var(--site-background-color); } +.submission-list-app, +.submission-list-footer-dummy { + background-color: #ffffff; +} + +.submission-list-app { + margin-top: calc(var(--header-height) + 28px); +} + +.submission-list-page .submission-list-app > .flex-container { + transform: none !important; +} + +html.submission-list-page, +body.submission-list-page, +html.submission-list-page #app, +html.submission-list-page #wrapper { + background-color: #ffffff; +} + +html:has(.content-app.submission-list-app), +body:has(.content-app.submission-list-app) { + background-color: #ffffff; +} + +html.submission-list-page { + --header-glass-bg: #ffffff; + --header-glass-border-color: #eef1f5; + --header-glass-shadow: none; +} + +html.submission-list-page #header .header-menu { + padding-right: 32px; + padding-left: 32px; +} + +html.submission-list-page #header .logo { + margin-left: 0; +} + +html.submission-list-page #header .header-menu > .drop-menu { + padding-right: 0 !important; +} + .ps { margin-top: 50px; background-color: var(--bg-color); diff --git a/frontend/src/pages/oj/views/submission/SubmissionList.vue b/frontend/src/pages/oj/views/submission/SubmissionList.vue index ef9a749d7..e120c7c99 100644 --- a/frontend/src/pages/oj/views/submission/SubmissionList.vue +++ b/frontend/src/pages/oj/views/submission/SubmissionList.vue @@ -1,91 +1,135 @@ diff --git a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue index 6c6340d1f..8b7bdcf39 100644 --- a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue +++ b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue @@ -1,25 +1,59 @@ diff --git a/frontend/src/pages/oj/views/submission/SubmissionTable.vue b/frontend/src/pages/oj/views/submission/SubmissionTable.vue index 8bf1975a2..af5aee6d9 100644 --- a/frontend/src/pages/oj/views/submission/SubmissionTable.vue +++ b/frontend/src/pages/oj/views/submission/SubmissionTable.vue @@ -5,6 +5,10 @@ export default { name: "SubmissionTable", components: { SubmissionListItem }, props: { + contestID: { + type: [String, Number], + default: "", + }, data: { type: Array, default: () => [], @@ -14,54 +18,210 @@ export default { default: false, }, }, + computed: { + skeletonRows() { + return Array.from({ length: 18 }, (_, index) => index) + }, + }, } From 0f60d135809564408472435ff425a2787a1f8178 Mon Sep 17 00:00:00 2001 From: Junyoung Yang Date: Thu, 25 Jun 2026 17:40:44 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=EC=B1=84=EC=A0=90=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=EB=A5=BC=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EB=A7=A4=ED=95=91=EC=9C=BC=EB=A1=9C=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/submission/serializers.py | 18 +----------------- backend/submission/tests.py | 2 -- .../oj/views/submission/SubmissionListItem.vue | 3 --- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/backend/submission/serializers.py b/backend/submission/serializers.py index e40a8e1f8..aebc978eb 100644 --- a/backend/submission/serializers.py +++ b/backend/submission/serializers.py @@ -1,4 +1,4 @@ -from .models import JudgeStatus, Submission +from .models import Submission from utils.api import serializers from utils.serializers import LanguageNameChoiceField @@ -35,7 +35,6 @@ class Meta: class SubmissionListSerializer(serializers.ModelSerializer): problem = serializers.SlugRelatedField(read_only=True, slug_field="_id") problem_id = serializers.SerializerMethodField() - result_display = serializers.SerializerMethodField() show_link = serializers.SerializerMethodField() user_avatar = serializers.SerializerMethodField() @@ -58,18 +57,3 @@ def get_user_avatar(self, obj): def get_problem_id(self, obj): return obj.problem._id - - def get_result_display(self, obj): - return { - JudgeStatus.COMPILE_ERROR: "컴파일 에러", - JudgeStatus.WRONG_ANSWER: "오답", - JudgeStatus.ACCEPTED: "정답", - JudgeStatus.CPU_TIME_LIMIT_EXCEEDED: "시간 초과", - JudgeStatus.REAL_TIME_LIMIT_EXCEEDED: "시간 초과", - JudgeStatus.MEMORY_LIMIT_EXCEEDED: "메모리 초과", - JudgeStatus.RUNTIME_ERROR: "런타임 에러", - JudgeStatus.SYSTEM_ERROR: "시스템 에러", - JudgeStatus.PENDING: "대기 중", - JudgeStatus.JUDGING: "채점 중", - JudgeStatus.PARTIALLY_ACCEPTED: "부분 정답", - }.get(obj.result, obj.result) diff --git a/backend/submission/tests.py b/backend/submission/tests.py index 8fac2b7ba..5978e5bb9 100644 --- a/backend/submission/tests.py +++ b/backend/submission/tests.py @@ -236,7 +236,6 @@ def test_get_submissions_general(self): sub for sub in resp.data["data"]["results"] if sub["id"] == submission.id ) self.assertEqual(result["problem_id"], self.problem._id) - self.assertEqual(result["result_display"], "대기 중") def test_get_submissions_filtered_by_problem(self): # 문제 필터링 테스트 (일반 문제만) @@ -298,7 +297,6 @@ def test_get_submissions_contest_myself(self): s for s in resp.data["data"]["results"] if s["id"] == sub.id ) self.assertEqual(result["problem_id"], self.contest_problem._id) - self.assertEqual(result["result_display"], "정답") def test_get_submissions_with_problem_not_exist(self): # problem_id 지정했으나 존재하지 않는 경우 에러 발생 diff --git a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue index 8b7bdcf39..bf4e00ec0 100644 --- a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue +++ b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue @@ -30,9 +30,6 @@ export default { return this.item.problem_id || this.item.problem }, resultDisplay() { - if (this.item.result_display) { - return this.item.result_display - } return this.$t( "m." + JUDGE_STATUS[this.item.result].name.replace(/ /g, "_"), ) From d785b8f8e1930de710151ff3c3bd35ca2da58cb5 Mon Sep 17 00:00:00 2001 From: Junyoung Yang Date: Thu, 25 Jun 2026 17:46:25 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=EC=B1=84=EC=A0=90=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/oj/App.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/pages/oj/App.vue b/frontend/src/pages/oj/App.vue index 4d4c160c9..ba3c1596c 100644 --- a/frontend/src/pages/oj/App.vue +++ b/frontend/src/pages/oj/App.vue @@ -295,10 +295,6 @@ a { margin-top: calc(var(--header-height) + 28px); } -.submission-list-page .submission-list-app > .flex-container { - transform: none !important; -} - html.submission-list-page, body.submission-list-page, html.submission-list-page #app, From 4f56ba078fdd0dfe4b60a6bbe7ca18242960626e Mon Sep 17 00:00:00 2001 From: Junyoung Yang Date: Thu, 25 Jun 2026 17:51:25 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=EC=A0=9C=EC=B6=9C=EC=9E=90=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=8F=99=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/oj/views/submission/SubmissionListItem.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue index bf4e00ec0..78974c25c 100644 --- a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue +++ b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue @@ -96,7 +96,7 @@ export default { goUser() { this.$router.push({ name: "user-home", - query: { username: this.item.username }, + params: { username: this.item.username }, }) }, getMemory(memory_cost) { From 109f39953d91d3433b379baf6a04296651b84f47 Mon Sep 17 00:00:00 2001 From: Junyoung Yang Date: Fri, 26 Jun 2026 13:33:53 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=EC=B1=84=EC=A0=90=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EB=B1=83=EC=A7=80=20=ED=85=8C=EB=91=90=EB=A6=AC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oj/views/submission/SubmissionListItem.vue | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue index 78974c25c..7909877e7 100644 --- a/frontend/src/pages/oj/views/submission/SubmissionListItem.vue +++ b/frontend/src/pages/oj/views/submission/SubmissionListItem.vue @@ -228,20 +228,18 @@ td { line-height: 1; letter-spacing: 0; cursor: pointer; - border: 1px solid var(--status-line); + border: 0; border-radius: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: var(--status-bg); transition: - border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease; } .status-badge:hover { - border-color: var(--status-line-strong); background: var(--status-bg-hover); } @@ -254,8 +252,6 @@ td { --status-ink: #216e4e; --status-bg: #eef8f1; --status-bg-hover: #e4f3e9; - --status-line: #d8eadf; - --status-line-strong: #bcdcc8; --status-focus: rgba(33, 110, 78, 0.2); } @@ -263,8 +259,6 @@ td { --status-ink: #7f5f01; --status-bg: #fff7e0; --status-bg-hover: #fff2c7; - --status-line: #f0dfb1; - --status-line-strong: #dec984; --status-focus: rgba(127, 95, 1, 0.2); } @@ -274,8 +268,6 @@ td { --status-ink: #9e4c00; --status-bg: #fff3e8; --status-bg-hover: #ffe9d3; - --status-line: #f2d2b5; - --status-line-strong: #e4b98d; --status-focus: rgba(158, 76, 0, 0.2); } @@ -284,8 +276,6 @@ td { --status-ink: #ae2e24; --status-bg: #fff1f0; --status-bg-hover: #ffe7e5; - --status-line: #efcbc7; - --status-line-strong: #e5ada7; --status-focus: rgba(174, 46, 36, 0.2); } @@ -293,8 +283,6 @@ td { --status-ink: #5d1f1a; --status-bg: #f8eeee; --status-bg-hover: #f3e3e3; - --status-line: #e7caca; - --status-line-strong: #d8aeae; --status-focus: rgba(93, 31, 26, 0.2); } @@ -304,8 +292,6 @@ td { --status-ink: #1558bc; --status-bg: #f0f6ff; --status-bg-hover: #e6f0ff; - --status-line: #d1e0f7; - --status-line-strong: #b3caec; --status-focus: rgba(21, 88, 188, 0.2); } @@ -313,8 +299,6 @@ td { --status-ink: #4f5b6f; --status-bg: #f8fafc; --status-bg-hover: #f1f5f9; - --status-line: #d8dee8; - --status-line-strong: #bdc7d5; --status-focus: rgba(105, 115, 134, 0.2); }