我的成绩支持删除本人记录:新增session/delete接口(仅本人、连带答题明细),历史行增加删除按钮与确认弹窗

This commit is contained in:
2026-09-09 20:08:20 +08:00
parent 62a8760292
commit 07399cf9c4
5 files changed
+95 -10

No files matched your search

+25
View File
@@ -1431,6 +1431,31 @@ func ApiQuiz(r *gin.RouterGroup) {
})
})
// 删除本人的成绩记录(连带答题明细)
r.POST("/session/delete", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
if !isAuth {
ReturnJson(ctx, "userCookieError", nil)
return
}
type FromDelete struct {
ID uint `json:"id"`
}
var from FromDelete
if err := decodeJSON(data, &from); err != nil || from.ID == 0 {
ReturnJson(ctx, "jsonErr", nil)
return
}
var session TabQuizSession
if err := models.DB.First(&session, from.ID).Error; err != nil || session.UserID != user.ID {
ReturnJson(ctx, "quiz_session_not_found", nil)
return
}
models.DB.Delete(&TabQuizAnswer{}, "session_id = ?", session.ID)
models.DB.Delete(&TabQuizSession{}, session.ID)
ReturnJson(ctx, "apiOK", nil)
})
// 错题重做:仅判分,不写入任何数据
r.POST("/redo", func(ctx *gin.Context) {
isAuth, user, data := AuthenticationAuthority(ctx)
+4
View File
@@ -53,6 +53,10 @@ export const quizApi = {
mySessions(params = {}) {
return api.post('/quiz/sessions', params)
},
/** 删除自己的成绩记录 */
deleteSession(id) {
return api.post('/quiz/session/delete', { id })
},
/** 答题回顾详情 */
getSession(id) {
return api.post('/quiz/session', { id })
+4 -1
View File
@@ -883,7 +883,10 @@
"learn_title": "Study Mode",
"learn_empty": "No questions in this bank yet",
"loading_more": "Loading...",
"all_loaded": "All questions loaded"
"all_loaded": "All questions loaded",
"delete_record": "Delete",
"delete_record_title": "Delete Score Record",
"delete_record_confirm": "Delete this score record? This cannot be undone."
},
"questions": {
"title": "Question Bank",
+4 -1
View File
@@ -884,7 +884,10 @@
"learn_title": "学习模式",
"learn_empty": "该题库暂无题目",
"loading_more": "加载中…",
"all_loaded": "已加载全部"
"all_loaded": "已加载全部",
"delete_record": "删除",
"delete_record_title": "删除成绩记录",
"delete_record_confirm": "确认删除该次成绩记录?删除后不可恢复。"
},
"questions": {
"title": "题库管理",
@@ -20,8 +20,11 @@ import {
IconChevronsLeft,
IconChevronsRight,
IconReload,
IconTrash,
} from '@tabler/icons-vue'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
usePageTitle('appname.quiz_bank')
const { t, locale } = useI18n()
const router = useRouter()
@@ -176,6 +179,36 @@ function redoWrong(s) {
router.push(`/quiz/redo/${s.id}`)
}
// 删除本人成绩记录
const delSessionTarget = ref(null)
const confirmDelSession = ref(false)
function askDeleteSession(s) {
delSessionTarget.value = s
confirmDelSession.value = true
}
async function doDeleteSession() {
if (!delSessionTarget.value) return
try {
const { errCode } = await quizApi.deleteSession(delSessionTarget.value.id)
if (errCode === 0) {
toast.success(t('message.delete_success'))
confirmDelSession.value = false
delSessionTarget.value = null
if (sessions.value.length === 1 && historyPage.value > 1) {
historyPage.value -= 1
}
fetchSessions()
fetchBoard()
} else {
toast.error(t('message.server_error'))
}
} catch {
// 拦截器已处理
}
}
// ── 排行榜 ──
async function fetchBoard() {
loadingBoard.value = true
@@ -372,14 +405,23 @@ onMounted(() => {
<td class="px-6 py-3 text-gray-500 dark:text-gray-400">{{ s.durationSec }} {{ t('quiz.seconds') }}</td>
<td class="whitespace-nowrap px-6 py-3 text-gray-500 dark:text-gray-400">{{ formatDate(s.createdAt) }}</td>
<td class="px-6 py-3">
<button
v-if="s.wrongCount > 0"
class="inline-flex items-center gap-1 whitespace-nowrap rounded-lg border border-blue-300 px-2.5 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-50 dark:border-blue-700 dark:text-blue-400 dark:hover:bg-blue-900/20"
@click.stop="redoWrong(s)"
>
<IconReload :size="13" />
{{ t('quiz.redo') }}
</button>
<div class="flex items-center gap-2">
<button
v-if="s.wrongCount > 0"
class="inline-flex items-center gap-1 whitespace-nowrap rounded-lg border border-blue-300 px-2.5 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-50 dark:border-blue-700 dark:text-blue-400 dark:hover:bg-blue-900/20"
@click.stop="redoWrong(s)"
>
<IconReload :size="13" />
{{ t('quiz.redo') }}
</button>
<button
class="rounded-lg border border-red-300 p-1.5 text-red-500 transition-colors hover:bg-red-50 dark:border-red-700 dark:text-red-400 dark:hover:bg-red-900/20"
:title="t('quiz.delete_record')"
@click.stop="askDeleteSession(s)"
>
<IconTrash :size="14" />
</button>
</div>
</td>
</tr>
<tr v-if="!loadingHistory && sessions.length === 0">
@@ -470,5 +512,13 @@ onMounted(() => {
</table>
</div>
</div>
<ConfirmDialog
v-model="confirmDelSession"
:title="t('quiz.delete_record_title')"
:message="t('quiz.delete_record_confirm')"
danger
@confirm="doDeleteSession"
/>
</div>
</template>