feat: 总览数据联调
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
export type LoginRole = 'super_admin' | 'doctor'
|
||||
export type LoginRole = 'super_admin' | 'doctor' | 'hospital_admin' | 'content_admin'
|
||||
|
||||
export interface LoginPayload {
|
||||
account: string
|
||||
|
||||
@@ -0,0 +1,649 @@
|
||||
export interface KpiOverview {
|
||||
institution_count?: number | null
|
||||
mau_institution?: number | null
|
||||
user_total?: number | null
|
||||
mau_user?: number | null
|
||||
}
|
||||
|
||||
export interface PlatformCoreOverview {
|
||||
train_new_month?: number | null
|
||||
train_new_mom?: number | null
|
||||
train_total?: number | null
|
||||
complete_rate?: number | null
|
||||
avg_score?: number | null
|
||||
}
|
||||
|
||||
export interface TrendOverview {
|
||||
months: string[]
|
||||
train_counts: number[]
|
||||
active_users?: number[]
|
||||
}
|
||||
|
||||
export interface UserComposeItem {
|
||||
role: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface HourlyAverageItem {
|
||||
hour: number
|
||||
avg: number
|
||||
}
|
||||
|
||||
export interface InstitutionDistributionItem {
|
||||
id: number
|
||||
institution: string
|
||||
users: number
|
||||
active: number
|
||||
}
|
||||
|
||||
export interface HospitalTrainRankItem {
|
||||
id: number
|
||||
institution: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface HospitalScoreRankItem {
|
||||
id: number
|
||||
institution: string
|
||||
avg_score: number
|
||||
}
|
||||
|
||||
export interface CaseTypeCountItem {
|
||||
case_type: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface CaseTypeUsageRateItem {
|
||||
case_type: string
|
||||
rate: number
|
||||
}
|
||||
|
||||
export interface CaseUsageItem {
|
||||
case_id: number
|
||||
title: string
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface CasePassRateItem {
|
||||
case_id: number
|
||||
title: string
|
||||
pass_rate: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface PlatformCaseAssetOverview {
|
||||
total?: number | null
|
||||
new_month?: number | null
|
||||
new_mom?: number | null
|
||||
type_dist: CaseTypeCountItem[]
|
||||
type_usage_rate: CaseTypeUsageRateItem[]
|
||||
top_used: CaseUsageItem[]
|
||||
low_pass: CasePassRateItem[]
|
||||
}
|
||||
|
||||
export interface PlatformOverview {
|
||||
kpi: KpiOverview
|
||||
core: PlatformCoreOverview
|
||||
trend: TrendOverview
|
||||
user_compose: UserComposeItem[]
|
||||
hourly_7d: HourlyAverageItem[]
|
||||
institution_dist: InstitutionDistributionItem[]
|
||||
hospital_rank: {
|
||||
by_train: HospitalTrainRankItem[]
|
||||
by_avg_score: HospitalScoreRankItem[]
|
||||
}
|
||||
case_asset: PlatformCaseAssetOverview
|
||||
}
|
||||
|
||||
export interface HospitalOverview {
|
||||
profile: {
|
||||
institution_id?: number | null
|
||||
name?: string
|
||||
logo?: string
|
||||
level?: string
|
||||
cooperation_days?: number | null
|
||||
}
|
||||
summary: {
|
||||
dept_count?: number | null
|
||||
doctor_count?: number | null
|
||||
student_count?: number | null
|
||||
train_total?: number | null
|
||||
complete_rate?: number | null
|
||||
avg_score?: number | null
|
||||
train_months: string[]
|
||||
train_monthly: number[]
|
||||
}
|
||||
dept_rank: Array<{
|
||||
id: number
|
||||
department: string
|
||||
case_count: number
|
||||
train_count: number
|
||||
effective_train: number
|
||||
active_users: number
|
||||
avg_score: number
|
||||
}>
|
||||
case_asset: {
|
||||
total?: number | null
|
||||
new_month?: number | null
|
||||
top_used: CaseUsageItem[]
|
||||
pass_high: CasePassRateItem[]
|
||||
pass_low: CasePassRateItem[]
|
||||
}
|
||||
competency: {
|
||||
student_avg?: number | null
|
||||
platform_avg?: number | null
|
||||
radar: CompetencyDimension[]
|
||||
radar_platform: CompetencyDimension[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface ContentOverview {
|
||||
summary: {
|
||||
case_total?: number | null
|
||||
pending_publish?: number | null
|
||||
case_new_month?: number | null
|
||||
case_new_mom?: number | null
|
||||
usage_rate?: number | null
|
||||
train_total?: number | null
|
||||
train_mom?: number | null
|
||||
}
|
||||
dist: {
|
||||
type_dist: CaseTypeCountItem[]
|
||||
type_train: Record<string, number>
|
||||
dept_dist: Array<{
|
||||
id: number
|
||||
department: string
|
||||
case_count: number
|
||||
used_count: number
|
||||
train_count: number
|
||||
}>
|
||||
difficulty_dist: Array<{
|
||||
difficulty: string
|
||||
case_count: number
|
||||
train_count: number
|
||||
}>
|
||||
}
|
||||
warning: CasePassRateItem[]
|
||||
hot: CaseUsageItem[]
|
||||
}
|
||||
|
||||
export interface TeachingStudentOverviewItem {
|
||||
id: number
|
||||
real_name: string
|
||||
username: string
|
||||
department: string
|
||||
train_total: number
|
||||
complete_rate: number
|
||||
avg_score: number
|
||||
weak_dimensions: string[]
|
||||
most_trained_type: string
|
||||
last_trained_at: string
|
||||
pending_tasks: number | null
|
||||
}
|
||||
|
||||
export interface CompetencyDimension {
|
||||
dimension: string
|
||||
score: number
|
||||
}
|
||||
|
||||
export interface TeachingOverview {
|
||||
students: TeachingStudentOverviewItem[]
|
||||
overview: {
|
||||
student_count?: number | null
|
||||
radar: CompetencyDimension[]
|
||||
students_avg?: number | null
|
||||
institution_avg?: number | null
|
||||
train_months: string[]
|
||||
train_monthly: number[]
|
||||
task_summary?: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export interface TrainingRecordListParams {
|
||||
token: string
|
||||
scope: 'platform' | 'teacher'
|
||||
search?: string
|
||||
user_id?: string
|
||||
case_id?: string
|
||||
status?: string
|
||||
training_mode?: string
|
||||
case_type?: string
|
||||
institution?: string
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
min_score?: string
|
||||
max_score?: string
|
||||
page?: number
|
||||
}
|
||||
|
||||
export interface TrainingRecordItem {
|
||||
id: string
|
||||
userId: string
|
||||
userName: string
|
||||
username: string
|
||||
phone: string
|
||||
caseId: string
|
||||
caseTitle: string
|
||||
status: string
|
||||
trainingMode: string
|
||||
caseType: string
|
||||
institution: string
|
||||
score: number | null
|
||||
createdAt: string
|
||||
completedAt: string
|
||||
duration: string
|
||||
raw: unknown
|
||||
}
|
||||
|
||||
export interface TrainingRecordListResult {
|
||||
records: TrainingRecordItem[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface StudentRankingItem {
|
||||
id: string
|
||||
name: string
|
||||
username: string
|
||||
department: string
|
||||
value: number | null
|
||||
score: number | null
|
||||
trainCount: number | null
|
||||
completed: number | null
|
||||
completeRate: number | null
|
||||
raw: unknown
|
||||
}
|
||||
|
||||
export interface StudentRankingResult {
|
||||
students: StudentRankingItem[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface StudentCompetency {
|
||||
profile: {
|
||||
id: string
|
||||
name: string
|
||||
username: string
|
||||
department: string
|
||||
}
|
||||
summary: {
|
||||
trainTotal: number | null
|
||||
completeRate: number | null
|
||||
avgScore: number | null
|
||||
lastTrainedAt: string
|
||||
}
|
||||
radar: CompetencyDimension[]
|
||||
trend: {
|
||||
months: string[]
|
||||
trainCounts: number[]
|
||||
scores: number[]
|
||||
}
|
||||
weakDimensions: CompetencyDimension[]
|
||||
raw: unknown
|
||||
}
|
||||
|
||||
const listKeys = ['results', 'list', 'rows', 'items', 'records', 'students', 'data']
|
||||
const totalKeys = ['count', 'total', 'total_count', 'totalCount']
|
||||
|
||||
function createAuthorization(token: string) {
|
||||
return /^Bearer\s+/i.test(token) ? token : `Bearer ${token}`
|
||||
}
|
||||
|
||||
function parseResponseText(text: string): unknown {
|
||||
if (!text) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function getMessageFromResponse(data: unknown): string {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return ''
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>
|
||||
const message = record.message || record.msg || record.detail
|
||||
if (typeof message === 'string') {
|
||||
return message
|
||||
}
|
||||
|
||||
return getMessageFromResponse(record.data)
|
||||
}
|
||||
|
||||
function unwrapData(data: unknown): unknown {
|
||||
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
||||
return data
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>
|
||||
const nested = record.data || record.result || record.payload
|
||||
if (nested && typeof nested === 'object') {
|
||||
const metaKeys = ['message', 'msg', 'code', 'success']
|
||||
const dataOnlyKeys = Object.keys(record).filter(key => !metaKeys.includes(key))
|
||||
if (dataOnlyKeys.length === 1 || ('data' in record && !('kpi' in record) && !('summary' in record))) {
|
||||
return unwrapData(nested)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async function requestJson<T>(url: string, token: string, fallbackMessage: string): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: createAuthorization(token)
|
||||
}
|
||||
})
|
||||
const text = await response.text()
|
||||
const data = parseResponseText(text)
|
||||
|
||||
if (!response.ok) {
|
||||
const message = getMessageFromResponse(data) || fallbackMessage
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
return unwrapData(data) as T
|
||||
}
|
||||
|
||||
function getRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {}
|
||||
}
|
||||
|
||||
function getFirst(record: Record<string, unknown>, keys: string[]): unknown {
|
||||
for (const key of keys) {
|
||||
const value = record[key]
|
||||
if (value !== undefined && value !== null) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getString(record: Record<string, unknown>, keys: string[], fallback = ''): string {
|
||||
const value = getFirst(record, keys)
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function getNumber(record: Record<string, unknown>, keys: string[]): number | null {
|
||||
const value = getFirst(record, keys)
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
return value
|
||||
}
|
||||
if (typeof value === 'string' && value.trim() && Number.isFinite(Number(value))) {
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function getTotal(record: Record<string, unknown>, fallback: number): number {
|
||||
for (const key of totalKeys) {
|
||||
const value = record[key]
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
if (typeof value === 'string' && Number.isFinite(Number(value))) {
|
||||
return Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function findListPayload(data: unknown): { items: unknown[]; total: number } {
|
||||
if (Array.isArray(data)) {
|
||||
return { items: data, total: data.length }
|
||||
}
|
||||
if (!data || typeof data !== 'object') {
|
||||
return { items: [], total: 0 }
|
||||
}
|
||||
|
||||
const record = data as Record<string, unknown>
|
||||
for (const key of listKeys) {
|
||||
const value = record[key]
|
||||
if (Array.isArray(value)) {
|
||||
return { items: value, total: getTotal(record, value.length) }
|
||||
}
|
||||
}
|
||||
|
||||
const nested = record.data || record.result || record.payload
|
||||
if (nested && typeof nested === 'object') {
|
||||
const payload = findListPayload(nested)
|
||||
return {
|
||||
items: payload.items,
|
||||
total: payload.total || getTotal(record, payload.items.length)
|
||||
}
|
||||
}
|
||||
|
||||
return { items: [], total: getTotal(record, 0) }
|
||||
}
|
||||
|
||||
function createQuery(params: Record<string, unknown>) {
|
||||
const query = new URLSearchParams()
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return
|
||||
}
|
||||
if (typeof value === 'string' && !value.trim()) {
|
||||
return
|
||||
}
|
||||
query.set(key, String(value).trim())
|
||||
})
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
function getNestedString(record: Record<string, unknown>, nestedKeys: string[], valueKeys: string[], fallback = '') {
|
||||
for (const key of nestedKeys) {
|
||||
const nested = getRecord(record[key])
|
||||
const value = getString(nested, valueKeys)
|
||||
if (value) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
function getNestedNumber(record: Record<string, unknown>, nestedKeys: string[], valueKeys: string[]) {
|
||||
for (const key of nestedKeys) {
|
||||
const nested = getRecord(record[key])
|
||||
const value = getNumber(nested, valueKeys)
|
||||
if (value !== null) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function normalizeTrainingRecord(item: unknown): TrainingRecordItem {
|
||||
const record = getRecord(item)
|
||||
const user = getRecord(getFirst(record, ['user', 'student', 'trainee']))
|
||||
const caseRecord = getRecord(getFirst(record, ['case', 'case_info', 'training_case']))
|
||||
|
||||
return {
|
||||
id: getString(record, ['id', 'record_id', 'recordId', 'uuid']),
|
||||
userId: getString(record, ['user_id', 'userId', 'student_id', 'studentId']) || getString(user, ['id', 'user_id']),
|
||||
userName: getString(record, ['user_name', 'real_name', 'realName', 'student_name', 'studentName']) || getString(user, ['real_name', 'realName', 'name']),
|
||||
username: getString(record, ['username', 'account']) || getString(user, ['username', 'account']),
|
||||
phone: getString(record, ['phone', 'mobile']) || getString(user, ['phone', 'mobile', 'username']),
|
||||
caseId: getString(record, ['case_id', 'caseId']) || getString(caseRecord, ['id', 'case_id']),
|
||||
caseTitle: getString(record, ['case_title', 'caseTitle', 'title']) || getString(caseRecord, ['title', 'name']),
|
||||
status: getString(record, ['status', 'state']),
|
||||
trainingMode: getString(record, ['training_mode', 'trainingMode', 'mode']),
|
||||
caseType: getString(record, ['case_type', 'caseType']) || getString(caseRecord, ['case_type', 'caseType']),
|
||||
institution: getString(record, ['institution', 'institution_name', 'institutionName', 'hospital', 'hospital_name', 'hospitalName']) ||
|
||||
getNestedString(user, ['institution', 'hospital'], ['name', 'institution', 'institution_name']),
|
||||
score: getNumber(record, ['score', 'final_score', 'finalScore', 'total_score', 'totalScore', 'normalized_score', 'normalizedScore', 'avg_score']),
|
||||
createdAt: getString(record, ['created_at', 'createdAt', 'start_time', 'startTime', 'trained_at', 'trainedAt']),
|
||||
completedAt: getString(record, ['completed_at', 'completedAt', 'end_time', 'endTime', 'finished_at', 'finishedAt']),
|
||||
duration: getString(record, ['duration', 'duration_text', 'durationText', 'elapsed', 'elapsed_seconds', 'elapsedSeconds']),
|
||||
raw: item
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRankingItem(item: unknown, dimension: string): StudentRankingItem {
|
||||
const record = getRecord(item)
|
||||
const user = getRecord(getFirst(record, ['user', 'student']))
|
||||
const dimensions = getRecord(getFirst(record, ['dimensions', 'dimension_scores', 'scores']))
|
||||
const dimensionValue = getNumber(record, [dimension]) ?? getNumber(dimensions, [dimension])
|
||||
const score = getNumber(record, ['score', 'avg_score', 'avgScore', 'students_avg'])
|
||||
const trainCount = getNumber(record, ['train_count', 'trainCount', 'train_total', 'trainTotal'])
|
||||
const completed = getNumber(record, ['completed', 'completed_count', 'completedCount', 'effective_train'])
|
||||
const completeRate = getNumber(record, ['complete_rate', 'completeRate', 'completed_rate'])
|
||||
const fallbackValue = dimension === 'train_count'
|
||||
? trainCount
|
||||
: dimension === 'completed'
|
||||
? completed ?? completeRate
|
||||
: score
|
||||
|
||||
return {
|
||||
id: getString(record, ['id', 'user_id', 'userId', 'student_id', 'studentId']) || getString(user, ['id', 'user_id']),
|
||||
name: getString(record, ['real_name', 'realName', 'name', 'student_name', 'studentName']) || getString(user, ['real_name', 'realName', 'name']),
|
||||
username: getString(record, ['username', 'account']) || getString(user, ['username', 'account']),
|
||||
department: getString(record, ['department', 'department_name', 'departmentName']) ||
|
||||
getNestedString(user, ['department'], ['name', 'department']),
|
||||
value: getNumber(record, ['value', 'rank_value', 'rankValue']) ?? dimensionValue ?? fallbackValue,
|
||||
score,
|
||||
trainCount,
|
||||
completed,
|
||||
completeRate,
|
||||
raw: item
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDimensionList(value: unknown): CompetencyDimension[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
.map(item => {
|
||||
const record = getRecord(item)
|
||||
const dimension = getString(record, ['dimension', 'name', 'label'])
|
||||
const score = getNumber(record, ['score', 'value', 'rate'])
|
||||
return dimension && score !== null ? { dimension, score } : null
|
||||
})
|
||||
.filter((item): item is CompetencyDimension => Boolean(item))
|
||||
}
|
||||
|
||||
const record = getRecord(value)
|
||||
return Object.entries(record)
|
||||
.map(([dimension, score]) => {
|
||||
const numericScore = typeof score === 'number' ? score : typeof score === 'string' && Number.isFinite(Number(score)) ? Number(score) : null
|
||||
return numericScore !== null ? { dimension, score: numericScore } : null
|
||||
})
|
||||
.filter((item): item is CompetencyDimension => Boolean(item))
|
||||
}
|
||||
|
||||
function firstDimensionList(...values: unknown[]): CompetencyDimension[] {
|
||||
for (const value of values) {
|
||||
const list = normalizeDimensionList(value)
|
||||
if (list.length) {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function normalizeCompetency(data: unknown, id: string | number): StudentCompetency {
|
||||
const root = getRecord(unwrapData(data))
|
||||
const student = getRecord(getFirst(root, ['student', 'profile', 'user']))
|
||||
const summary = getRecord(getFirst(root, ['summary', 'overview']))
|
||||
const competency = getRecord(root.competency)
|
||||
const trend = getRecord(root.trend)
|
||||
const radar = firstDimensionList(root.radar, competency.radar, root.dimension_scores, root.dimensions, summary.radar)
|
||||
const explicitWeak = firstDimensionList(root.weak_dimensions, summary.weak_dimensions)
|
||||
const weakDimensions = explicitWeak.length ? explicitWeak : radar.filter(item => item.score < 60)
|
||||
|
||||
return {
|
||||
profile: {
|
||||
id: getString(root, ['id', 'student_id', 'studentId', 'user_id', 'userId'], String(id)) || getString(student, ['id', 'user_id'], String(id)),
|
||||
name: getString(root, ['real_name', 'realName', 'name', 'student_name', 'studentName']) || getString(student, ['real_name', 'realName', 'name']),
|
||||
username: getString(root, ['username', 'account']) || getString(student, ['username', 'account']),
|
||||
department: getString(root, ['department', 'department_name', 'departmentName']) || getString(student, ['department', 'department_name', 'departmentName'])
|
||||
},
|
||||
summary: {
|
||||
trainTotal: getNumber(summary, ['train_total', 'trainTotal']) ?? getNumber(root, ['train_total', 'trainTotal']),
|
||||
completeRate: getNumber(summary, ['complete_rate', 'completeRate']) ?? getNumber(root, ['complete_rate', 'completeRate']),
|
||||
avgScore: getNumber(summary, ['avg_score', 'avgScore', 'student_avg']) ?? getNumber(root, ['avg_score', 'avgScore', 'student_avg']),
|
||||
lastTrainedAt: getString(summary, ['last_trained_at', 'lastTrainedAt']) || getString(root, ['last_trained_at', 'lastTrainedAt'])
|
||||
},
|
||||
radar,
|
||||
trend: {
|
||||
months: normalizeStringArray(getFirst(trend, ['months', 'train_months']) ?? getFirst(summary, ['months', 'train_months']) ?? root.train_months),
|
||||
trainCounts: normalizeNumberArray(getFirst(trend, ['train_counts', 'train_monthly']) ?? getFirst(summary, ['train_counts', 'train_monthly']) ?? root.train_monthly),
|
||||
scores: normalizeNumberArray(getFirst(trend, ['scores', 'avg_scores', 'score_monthly']) ?? getFirst(summary, ['scores', 'avg_scores', 'score_monthly']))
|
||||
},
|
||||
weakDimensions,
|
||||
raw: data
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value) ? value.map(item => String(item)) : []
|
||||
}
|
||||
|
||||
function normalizeNumberArray(value: unknown): number[] {
|
||||
return Array.isArray(value)
|
||||
? value.map(item => (typeof item === 'number' ? item : Number(item))).filter(item => Number.isFinite(item))
|
||||
: []
|
||||
}
|
||||
|
||||
export async function fetchPlatformOverview(token: string): Promise<PlatformOverview> {
|
||||
return requestJson<PlatformOverview>('/server/api/cms/stats/overview/', token, '获取平台概览失败')
|
||||
}
|
||||
|
||||
export async function fetchHospitalOverview(token: string): Promise<HospitalOverview> {
|
||||
return requestJson<HospitalOverview>('/server/api/cms/stats/hospital/overview/', token, '获取医院概览失败')
|
||||
}
|
||||
|
||||
export async function fetchContentOverview(token: string): Promise<ContentOverview> {
|
||||
return requestJson<ContentOverview>('/server/api/cms/stats/content/overview/', token, '获取内容概览失败')
|
||||
}
|
||||
|
||||
export async function fetchTeachingOverview(token: string): Promise<TeachingOverview> {
|
||||
return requestJson<TeachingOverview>('/server/api/cms/stats/teaching/overview/', token, '获取教学概览失败')
|
||||
}
|
||||
|
||||
export async function fetchTrainingRecords(params: TrainingRecordListParams): Promise<TrainingRecordListResult> {
|
||||
const { token, scope, ...queryParams } = params
|
||||
const query = createQuery(queryParams)
|
||||
const baseUrl = scope === 'teacher' ? '/server/api/cms/students/training-records/' : '/server/api/cms/training-records/'
|
||||
const data = await requestJson<unknown>(`${baseUrl}${query.toString() ? `?${query.toString()}` : ''}`, token, '获取训练记录失败')
|
||||
const payload = findListPayload(data)
|
||||
|
||||
return {
|
||||
records: payload.items.map(normalizeTrainingRecord),
|
||||
total: payload.total
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchStudentCompetency(token: string, id: string | number): Promise<StudentCompetency> {
|
||||
const data = await requestJson<unknown>(`/server/api/cms/students/${id}/competency/`, token, '获取学生能力画像失败')
|
||||
return normalizeCompetency(data, id)
|
||||
}
|
||||
|
||||
export async function fetchStudentRanking(token: string, dimension = 'score'): Promise<StudentRankingResult> {
|
||||
const query = createQuery({ dimension })
|
||||
const data = await requestJson<unknown>(`/server/api/cms/students/ranking/?${query.toString()}`, token, '获取学生排行榜失败')
|
||||
const payload = findListPayload(data)
|
||||
|
||||
return {
|
||||
students: payload.items.map(item => normalizeRankingItem(item, dimension)),
|
||||
total: payload.total
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user