import { For, Show, createEffect, createMemo, createSignal, type JSX, } from "solid-js"; import type { CourseKind, RecordType, WorkListItem, ExamListItem } from "~/service/wk"; import type { AccountItem } from "~/store/account"; import type { CourseType } from "~/types/Course"; import type { RecordItem } from "~/service/wk"; const stripHtml = (value: string) => value.replace(/<[^>]+>/g, "").trim(); type RecordTypeOption = { label: string; value: RecordType; }; type CourseRecordTypeOption = { label: string; value: CourseKind; }; type RecordFilter = "all" | "unlearned" | "learned"; interface CourseWorkspaceProps { selectedAccount: AccountItem | null; selectedCourseList: CourseType[]; selectedCourseId: number | null; selectedCourse: CourseType | null; recordType: RecordType; courseKind: CourseKind; currentCourseKindLabel: string; showingCachedRecords: boolean; recordTypeOptions: RecordTypeOption[]; courseRecordTypeOptions: CourseRecordTypeOption[]; records: RecordItem[]; workList: WorkListItem[]; examList: ExamListItem[]; studyLogs: string[]; recordsLoading: boolean; recordError: string; isRefreshingRecords: boolean; isRefreshingCourseRecords: boolean; isRunningStudy: boolean; isRefreshingLogs: boolean; autoScrollLogs: boolean; showLogTimestamps: boolean; densityMode: "comfortable" | "compact"; logFontSize: number; onSelectCourse: (courseId: number) => void; onRefreshRecords: () => void; onRefreshCourseRecords: () => void; onChangeRecordType: (value: RecordType) => void; onChangeCourseRecordType: (value: CourseKind) => void; onStartStudy: () => void; onStopStudy: () => void; onRefreshLogs: () => void; onClearLogs: () => void; renderRecordState: (value: string) => string; } const EmptyState = (props: { children: JSX.Element }) => (
{props.children}
); const extractTimestamp = (message: string) => { const match = message.match(/^\[(\d{2}:\d{2}:\d{2})\]\s*/); return match?.[1] ?? null; }; const stripTimestamp = (message: string) => { return message.replace(/^\[(\d{2}:\d{2}:\d{2})\]\s*/, ""); }; const CourseWorkspace = (props: CourseWorkspaceProps) => { let logContainerRef: HTMLDivElement | undefined; const [recordFilter, setRecordFilter] = createSignal("all"); createEffect(() => { props.studyLogs.length; if (!props.autoScrollLogs) { return; } requestAnimationFrame(() => { const element = logContainerRef; if (element) { element.scrollTo({ top: element.scrollHeight, behavior: "smooth" }); } }); }); createEffect(() => { if (logContainerRef) { logContainerRef.style.fontSize = `${props.logFontSize}px`; } }); const compact = () => props.densityMode === "compact"; const recordStats = createMemo(() => { const learned = props.records.filter((record) => { const stateText = props.renderRecordState(record.state) || "未知状态"; return stateText.includes("已学") || record.progress === "1.00"; }).length; const total = props.records.length; return { total, learned, unlearned: Math.max(0, total - learned), }; }); const filteredRecords = createMemo(() => { if (recordFilter() === "all") { return props.records; } return props.records.filter((record) => { const stateText = props.renderRecordState(record.state) || "未知状态"; const learned = stateText.includes("已学") || record.progress === "1.00"; return recordFilter() === "learned" ? learned : !learned; }); }); return (

课程工作台

课程、记录与日志统一查看

{props.selectedAccount?.user.name}
选择左侧账号后,这里会显示课程、记录和运行日志。 } >

课程列表

点击课程查看对应记录

当前“{props.currentCourseKindLabel} ”下没有课程,请切换分类或刷新课程列表。 {(course) => { const selected = () => course.id === props.selectedCourseId; return ( ); }}

记录列表

{props.selectedCourse ? props.selectedCourse.name : "请选择课程"}

当前显示的是本地缓存记录。

当前筛选:{recordFilter() === "all" ? "全部" : recordFilter() === "unlearned" ? "未学" : "已学"}

0}>
共 {props.workList.length} 条作业记录
0}>
共 {props.examList.length} 条考试记录
正在加载记录...
{props.recordError}
点击左侧课程后,在这里查看对应记录。 {props.records.length === 0 ? "当前分类下没有记录。" : "当前筛选下没有记录。"} 当前课程下没有作业记录。 当前课程下没有考试记录。
{(record) => { const stateText = props.renderRecordState(record.state) || "未知状态"; const learned = stateText.includes("已学") || record.progress === "1.00"; return (

{record.name}

记录 ID:{record.id} | 章节:{record.chapterId}

{stateText}

视频时长:{record.videoDuration}

学习秒数:{record.duration}

学习进度:{record.progress}

开始时间:{record.beginTime || "-"}

完成时间:{record.finalTime || "-"}

查看次数:{record.viewCount}

); }}
{(work) => { const stateRaw = stripHtml(work.state); const stateText = stateRaw || "未做"; const done = stateRaw.includes("已阅") || stateRaw.includes("已提交") || stateRaw.includes("已完成"); return (

{work.title || work.name}

作业 ID:{work.id} | 章节:{work.chapterId}

{stateText}

类型:{work.typeName || work.type || "-"}

总分:{work.score || "-"}

得分:{stripHtml(work.finalScore) || "-"}

题目数:{work.topicNumber || "-"}

添加时间:{work.addTime || "-"}

完成时间:{work.finishTime !== "-" ? work.finishTime : "-"}

); }}
{(exam) => { const stateRaw = stripHtml(exam.state); const stateText = stateRaw || "未做"; const done = stateRaw.includes("已阅") || stateRaw.includes("已提交") || stateRaw.includes("已完成"); return (

{exam.title || exam.name}

考试 ID:{exam.id} | 章节:{exam.chapterId}

{stateText}

限时:{exam.limitedTime ? `${exam.limitedTime}分钟` : "-"}

总分:{exam.score || "-"}

得分:{stripHtml(exam.finalScore) || "-"}

题目数:{exam.topicNumber || "-"}

添加时间:{exam.addTime || "-"}

完成时间:{exam.finishTime !== "-" ? exam.finishTime : "-"}

); }}

运行日志

输出会自动滚动到最新位置

0} fallback={

暂无日志输出。

} >
{(log, index) => (

[{index() + 1}]{" "} {props.showLogTimestamps && extractTimestamp(log) ? `${extractTimestamp(log)} ` : ""} {stripTimestamp(log)}

)}
); }; export default CourseWorkspace;