add ai report
This commit is contained in:
126
src/admin/ai/list.vue
Normal file
126
src/admin/ai/list.vue
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
<template>
|
||||||
|
<n-flex justify="space-between" class="titleWrapper">
|
||||||
|
<h2 class="title">AI 学习分析报告</h2>
|
||||||
|
<n-input
|
||||||
|
v-model:value="query.username"
|
||||||
|
clearable
|
||||||
|
placeholder="输入用户名筛选"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</n-flex>
|
||||||
|
<n-data-table striped :columns="columns" :data="reports" />
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:limit="query.limit"
|
||||||
|
v-model:page="query.page"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-modal v-model:show="showModal" preset="card" title="分析报告详情" style="width: 800px; max-width: 95vw">
|
||||||
|
<n-spin :show="loadingDetail">
|
||||||
|
<div v-if="detail" class="detail">
|
||||||
|
<n-descriptions :column="2" bordered size="small" class="meta">
|
||||||
|
<n-descriptions-item label="用户">{{ detail.username }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="班级">{{ detail.class_name || "-" }}</n-descriptions-item>
|
||||||
|
<n-descriptions-item label="时间" :span="2">{{ parseTime(detail.create_time, "YYYY-MM-DD HH:mm:ss") }}</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
<n-scrollbar style="max-height: 60vh; margin-top: 12px">
|
||||||
|
<pre class="analysis">{{ detail.analysis }}</pre>
|
||||||
|
</n-scrollbar>
|
||||||
|
</div>
|
||||||
|
</n-spin>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Pagination from "shared/components/Pagination.vue"
|
||||||
|
import { parseTime } from "utils/functions"
|
||||||
|
import { getAIReportList, getAIReportDetail } from "../api"
|
||||||
|
import { NButton } from "naive-ui"
|
||||||
|
|
||||||
|
interface ReportItem {
|
||||||
|
id: number
|
||||||
|
create_time: string
|
||||||
|
username: string
|
||||||
|
class_name: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReportDetail extends ReportItem {
|
||||||
|
analysis: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const reports = ref<ReportItem[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const query = reactive({ limit: 10, page: 1, username: "" })
|
||||||
|
|
||||||
|
const showModal = ref(false)
|
||||||
|
const loadingDetail = ref(false)
|
||||||
|
const detail = ref<ReportDetail | null>(null)
|
||||||
|
|
||||||
|
const columns: DataTableColumn<ReportItem>[] = [
|
||||||
|
{ title: "ID", key: "id", width: 80 },
|
||||||
|
{ title: "用户名", key: "username", width: 150 },
|
||||||
|
{ title: "班级", key: "class_name", width: 150, render: (row) => row.class_name || "-" },
|
||||||
|
{
|
||||||
|
title: "生成时间",
|
||||||
|
key: "create_time",
|
||||||
|
width: 200,
|
||||||
|
render: (row) => parseTime(row.create_time, "YYYY-MM-DD HH:mm:ss"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
width: 80,
|
||||||
|
render: (row) =>
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{ size: "small", type: "primary", onClick: () => openDetail(row.id) },
|
||||||
|
() => "查看",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async function listReports() {
|
||||||
|
const offset = (query.page - 1) * query.limit
|
||||||
|
const res = await getAIReportList(offset, query.limit, query.username)
|
||||||
|
reports.value = res.data.results
|
||||||
|
total.value = res.data.total
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDetail(id: number) {
|
||||||
|
showModal.value = true
|
||||||
|
loadingDetail.value = true
|
||||||
|
detail.value = null
|
||||||
|
try {
|
||||||
|
const res = await getAIReportDetail(id)
|
||||||
|
detail.value = res.data
|
||||||
|
} finally {
|
||||||
|
loadingDetail.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(listReports)
|
||||||
|
watch(() => [query.page, query.limit], listReports)
|
||||||
|
watchDebounced(() => query.username, listReports, { debounce: 500, maxWait: 1000 })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.titleWrapper {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.detail .meta {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.analysis {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -490,3 +490,14 @@ export function getTopACTrend(params: {
|
|||||||
}) {
|
}) {
|
||||||
return http.get("admin/problem/top_ac_trend", { params })
|
return http.get("admin/problem/top_ac_trend", { params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI 学习分析报告
|
||||||
|
export function getAIReportList(offset = 0, limit = 10, username = "") {
|
||||||
|
return http.get("admin/ai/reports", {
|
||||||
|
params: { paging: true, offset, limit, username: username || undefined },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAIReportDetail(id: number) {
|
||||||
|
return http.get("admin/ai/reports", { params: { id } })
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RouteRecordRaw } from "vue-router"
|
import type { RouteRecordRaw } from "vue-router"
|
||||||
|
|
||||||
export const ojs: RouteRecordRaw = {
|
export const ojs: RouteRecordRaw = {
|
||||||
path: "/",
|
path: "/",
|
||||||
@@ -315,5 +315,11 @@ export const admins: RouteRecordRaw = {
|
|||||||
props: true,
|
props: true,
|
||||||
meta: { requiresTeacherAdmin: true },
|
meta: { requiresTeacherAdmin: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "ai/reports",
|
||||||
|
name: "admin ai reports",
|
||||||
|
component: () => import("admin/ai/list.vue"),
|
||||||
|
meta: { requiresTeacherAdmin: true },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ const options = computed<MenuOption[]>(() => {
|
|||||||
),
|
),
|
||||||
key: "admin problemset list",
|
key: "admin problemset list",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: () =>
|
||||||
|
h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: "/admin/ai/reports" },
|
||||||
|
{ default: () => "AI报告" },
|
||||||
|
),
|
||||||
|
key: "admin ai reports",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +141,15 @@ const options = computed<MenuOption[]>(() => {
|
|||||||
),
|
),
|
||||||
key: "admin tutorial list",
|
key: "admin tutorial list",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: () =>
|
||||||
|
h(
|
||||||
|
RouterLink,
|
||||||
|
{ to: "/admin/ai/reports" },
|
||||||
|
{ default: () => "AI报告" },
|
||||||
|
),
|
||||||
|
key: "admin ai reports",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +170,7 @@ const active = computed(() => {
|
|||||||
if (path.startsWith("/admin/comment")) return "admin comment list"
|
if (path.startsWith("/admin/comment")) return "admin comment list"
|
||||||
if (path.startsWith("/admin/announcement")) return "admin announcement list"
|
if (path.startsWith("/admin/announcement")) return "admin announcement list"
|
||||||
if (path.startsWith("/admin/tutorial")) return "admin tutorial list"
|
if (path.startsWith("/admin/tutorial")) return "admin tutorial list"
|
||||||
|
if (path.startsWith("/admin/ai")) return "admin ai reports"
|
||||||
return route.name as string
|
return route.name as string
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user