add login summary
Some checks failed
Deploy / deploy (push) Has been cancelled

This commit is contained in:
2026-01-18 20:10:54 +08:00
parent 8747974f8d
commit e54aab64d2
5 changed files with 152 additions and 1 deletions

View File

@@ -6,6 +6,7 @@ import { useConfigStore } from "shared/store/config"
import { useConfigUpdate } from "shared/composables/configUpdate"
import { useMaxKB } from "shared/composables/maxkb"
import { useUserStore } from "shared/store/user"
import LoginSummaryModal from "shared/components/LoginSummaryModal.vue"
const isDark = useDark()
const configStore = useConfigStore()
@@ -60,6 +61,7 @@ provide("hljs", hljsInstance)
>
<n-message-provider>
<router-view></router-view>
<LoginSummaryModal />
</n-message-provider>
</n-config-provider>
</template>

View File

@@ -297,6 +297,10 @@ export function getAIHeatmapData() {
return http.get("ai/heatmap")
}
export function getAILoginSummary() {
return http.get("ai/login_summary")
}
// ==================== 流程图相关API ====================
export function submitFlowchart(data: {

View File

@@ -4,10 +4,12 @@ import { storeToRefs } from "pinia"
import { useAuthModalStore } from "../store/authModal"
import { useConfigStore } from "../store/config"
import { useUserStore } from "../store/user"
import { useLoginSummaryStore } from "../store/loginSummary"
const userStore = useUserStore()
const configStore = useConfigStore()
const authStore = useAuthModalStore()
const loginSummaryStore = useLoginSummaryStore()
const {
loginModalOpen,
@@ -60,7 +62,8 @@ async function submit() {
}
if (!msg.value) {
authStore.closeLoginModal()
userStore.getMyProfile()
await userStore.getMyProfile()
loginSummaryStore.open()
}
}
})

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
import { MdPreview } from "md-editor-v3"
import "md-editor-v3/lib/preview.css"
import { useBreakpoints } from "shared/composables/breakpoints"
import { useLoginSummaryStore } from "shared/store/loginSummary"
import { parseTime } from "utils/functions"
const loginSummaryStore = useLoginSummaryStore()
const { isDesktop } = useBreakpoints()
const rangeText = computed(() => {
const summary = loginSummaryStore.summary
if (!summary?.start || !summary?.end) {
return ""
}
const start = parseTime(summary.start, "YYYY-MM-DD HH:mm")
const end = parseTime(summary.end, "YYYY-MM-DD HH:mm")
return `${start} - ${end}`
})
const hasAnalysis = computed(() => !!loginSummaryStore.analysis)
</script>
<template>
<n-modal
v-model:show="loginSummaryStore.show"
preset="card"
title="登录速报"
style="width: min(760px, 92vw)"
>
<n-spin :show="loginSummaryStore.loading" size="small">
<n-flex vertical size="large">
<n-text v-if="rangeText">统计区间{{ rangeText }}</n-text>
<n-grid :cols="isDesktop ? 3 : 1" :x-gap="16" :y-gap="16">
<n-gi>
<n-statistic
label="新增题目"
:value="loginSummaryStore.summary?.new_problem_count ?? 0"
/>
</n-gi>
<n-gi>
<n-statistic
label="提交次数"
:value="loginSummaryStore.summary?.submission_count ?? 0"
/>
</n-gi>
<n-gi>
<n-statistic
label="AC 次数"
:value="loginSummaryStore.summary?.accepted_count ?? 0"
/>
</n-gi>
<n-gi>
<n-statistic
label="AC 题目数"
:value="loginSummaryStore.summary?.solved_count ?? 0"
/>
</n-gi>
<n-gi>
<n-statistic
label="流程图提交"
:value="loginSummaryStore.summary?.flowchart_submission_count ?? 0"
/>
</n-gi>
</n-grid>
<n-divider>AI 分析</n-divider>
<n-alert
v-if="loginSummaryStore.analysisError"
type="warning"
:show-icon="false"
>
{{ loginSummaryStore.analysisError }}
</n-alert>
<MdPreview
v-if="hasAnalysis"
:model-value="loginSummaryStore.analysis"
/>
<n-empty
v-else
description="提交数少于 3 次,暂不生成 AI 分析"
/>
</n-flex>
</n-spin>
</n-modal>
</template>

View File

@@ -0,0 +1,55 @@
import { getAILoginSummary } from "oj/api"
interface LoginSummary {
start: string
end: string
new_problem_count: number
submission_count: number
accepted_count: number
solved_count: number
flowchart_submission_count: number
}
export const useLoginSummaryStore = defineStore("loginSummary", () => {
const show = ref(false)
const loading = ref(false)
const summary = ref<LoginSummary | null>(null)
const analysis = ref("")
const analysisError = ref("")
async function fetchSummary() {
loading.value = true
analysis.value = ""
analysisError.value = ""
try {
const res = await getAILoginSummary()
summary.value = res.data.summary
analysis.value = res.data.analysis || ""
analysisError.value = res.data.analysis_error || ""
} catch (err) {
analysisError.value = "获取登录统计失败,请稍后再试"
} finally {
loading.value = false
}
}
async function open() {
show.value = true
await fetchSummary()
}
function close() {
show.value = false
}
return {
show,
loading,
summary,
analysis,
analysisError,
fetchSummary,
open,
close,
}
})