feat: 联调

This commit is contained in:
王天骄
2026-06-11 12:12:55 +08:00
parent 2192b855a1
commit cdbe16dde3
35 changed files with 918 additions and 217 deletions
+163
View File
@@ -0,0 +1,163 @@
import { FASTAPI_BASE_URL, authHeaders, readError, type ApiEnvelope, type ScoreType } from './session'
import type { EvaluationResult } from './assessment'
export type TeachingAnswer = {
question_id: number | string
selected_answer: string
}
export type TeachingCaseOption = {
key: string
value: string
text: string
}
export type TeachingCaseQuestion = {
id: number | string
question: string
options: TeachingCaseOption[]
correctAnswer?: string
analysis?: string
note?: string
videoTitle?: string
videoDesc?: string
videoUrl?: string
}
type TeachingEvaluationPayload = {
case_id: number
answers: TeachingAnswer[]
score_type?: ScoreType
}
type TeachingCaseItemsResponse = {
items?: unknown[]
questions?: unknown[]
results?: unknown[]
list?: unknown[]
}
export async function fetchTeachingCaseItems(caseId: number) {
const response = await fetch(`${FASTAPI_BASE_URL}/teaching/cases/${caseId}/items`, {
method: 'GET',
headers: authHeaders()
})
if (!response.ok) {
throw new Error(await readError(response))
}
const result = (await response.json()) as ApiEnvelope<TeachingCaseItemsResponse | unknown[]>
if (result.code !== 'OK' || !result.data) {
throw new Error(result.message || '题目列表加载失败')
}
const rawItems = Array.isArray(result.data)
? result.data
: result.data.items || result.data.questions || result.data.results || result.data.list || []
return rawItems.map(normalizeTeachingQuestion).filter((item): item is TeachingCaseQuestion => Boolean(item))
}
export async function generateTeachingEvaluation(payload: TeachingEvaluationPayload) {
const response = await fetch(`${FASTAPI_BASE_URL}/teaching/evaluation`, {
method: 'POST',
headers: authHeaders(),
body: JSON.stringify(payload)
})
if (!response.ok) {
throw new Error(await readError(response))
}
const result = (await response.json()) as ApiEnvelope<EvaluationResult>
if (result.code !== 'OK' || !result.data?.evaluation_id) {
throw new Error(result.message || '教学评价生成失败')
}
return result.data
}
function normalizeTeachingQuestion(item: unknown) {
if (!item || typeof item !== 'object') return null
const source = item as Record<string, unknown>
const video = readObject(source.video)
const id = readId(source)
const question = readString(source, ['question', 'title', 'stem', 'content', 'text'])
const options = normalizeOptions(source.options || source.choices || source.answers)
if (!id || !question || options.length === 0) return null
return {
id,
question,
options,
correctAnswer: readString(source, ['correct_answer', 'correctAnswer', 'answer', 'right_answer']),
analysis: readString(source, ['analysis', 'explanation', '解析']),
note: readString(source, ['note', 'hint', 'comment']),
videoTitle: readString(video, ['title', 'name']) || readString(source, ['video_title', 'videoTitle']),
videoDesc: readString(video, ['description', 'desc']) || readString(source, ['video_desc', 'videoDesc', 'video_description']),
videoUrl: readString(video, ['url']) || readString(source, ['video_url', 'videoUrl'])
}
}
function readObject(value: unknown) {
if (value && typeof value === 'object' && !Array.isArray(value)) return value as Record<string, unknown>
return {}
}
function readId(source: Record<string, unknown>) {
const value = source.question_id || source.id || source.item_id
if (typeof value === 'number' || typeof value === 'string') return value
return ''
}
function normalizeOptions(value: unknown): TeachingCaseOption[] {
if (Array.isArray(value)) {
return value.map((item, index) => normalizeOption(item, index)).filter((item): item is TeachingCaseOption => Boolean(item))
}
if (value && typeof value === 'object') {
return Object.entries(value as Record<string, unknown>).map(([key, text]) => ({
key,
value: key,
text: typeof text === 'string' ? text : String(text || '')
})).filter(item => item.text)
}
return []
}
function normalizeOption(item: unknown, index: number) {
const fallbackKey = String.fromCharCode(65 + index)
if (typeof item === 'string') {
return {
key: fallbackKey,
value: fallbackKey,
text: item
}
}
if (!item || typeof item !== 'object') return null
const source = item as Record<string, unknown>
const value = readString(source, ['value', 'key', 'option', 'option_key', 'id']) || fallbackKey
const key = readString(source, ['key', 'option', 'option_key']) || value
const text = readString(source, ['label', 'text', 'content', 'option_text', 'name']) || value
return {
key,
value,
text
}
}
function readString(source: Record<string, unknown>, keys: string[]) {
for (const key of keys) {
const value = source[key]
if (typeof value === 'string' && value.trim()) return value
if (typeof value === 'number') return String(value)
}
return ''
}