fix: 修复问题

- 修复刷课错误不会停止
- 添加课程刷新
- 添加课程列表、记录列表缓存
- 显示当前版本
This commit is contained in:
2026-03-28 19:22:31 +08:00
parent 6325b84ca0
commit 9e7131f210
8 changed files with 347 additions and 32 deletions

View File

@@ -2,6 +2,7 @@ import {
createEffect,
createMemo,
createSignal,
on,
onCleanup,
onMount,
} from "solid-js";
@@ -55,6 +56,12 @@ const parseDurationToSeconds = (value: string) => {
return hours * 3600 + minutes * 60 + seconds;
};
const createRecordCacheKey = (
accountId: string,
courseId: number,
recordType: RecordType,
) => `${accountId}::${courseId}::${recordType || "course"}`;
const Account = () => {
const [storeState, setStoreState] = createSignal(accountStore.getState());
const [settingsState, setSettingsState] = createSignal(
@@ -71,6 +78,7 @@ const Account = () => {
const [recordsLoading, setRecordsLoading] = createSignal(false);
const [recordError, setRecordError] = createSignal("");
const [isRefreshingRecords, setIsRefreshingRecords] = createSignal(false);
const [isRefreshingCourses, setIsRefreshingCourses] = createSignal(false);
const [isRefreshingLogs, setIsRefreshingLogs] = createSignal(false);
const [hasRestoredRecords, setHasRestoredRecords] = createSignal(false);
const accountClients = new Map<string, ReturnType<typeof createWkClient>>();
@@ -95,8 +103,10 @@ const Account = () => {
const selectedAccountId = createMemo(() => storeState().selectedAccountId);
const expandedAccountId = createMemo(() => storeState().expandedAccountId);
const selectedCourseId = createMemo(() => storeState().selectedCourseId);
const courseKind = createMemo(() => storeState().courseKind);
const recordType = createMemo(() => storeState().recordType);
const records = createMemo(() => storeState().records);
const recordCacheMap = createMemo(() => storeState().recordCacheMap);
const studyLogsMap = createMemo(() => storeState().studyLogsMap);
const runningStudyMap = createMemo(() => storeState().runningStudyMap);
const mergedHostOptions = createMemo(() =>
@@ -111,6 +121,24 @@ const Account = () => {
const defaultHost = createMemo(
() => mergedHostOptions()[0]?.host ?? "cqcst.leykeji.com",
);
const currentCourseKindLabel = createMemo(
() =>
statusOptions.find((item) => item.value === courseKind())?.label ??
courseKind(),
);
const showingCachedRecords = createMemo(() => {
const accountId = selectedAccountId();
const courseId = selectedCourseId();
if (!accountId || !courseId || recordsLoading() || isRefreshingRecords()) {
return false;
}
return (
createRecordCacheKey(accountId, courseId, recordType()) in
recordCacheMap()
);
});
const selectedAccount = createMemo(() => {
return accounts().find((item) => item.id === selectedAccountId()) ?? null;
@@ -203,8 +231,9 @@ const Account = () => {
...target,
sessionId: res.data.session_id,
user: res.data.user,
courses: res.data.courses,
courses: res.data.courses ?? target.courses,
});
await loadCourses(target.id, target.status);
appendStudyLog(`会话失效,已重新登录:${target.user.name}`, target.id);
return true;
} catch (error) {
@@ -265,10 +294,11 @@ const Account = () => {
token: payload.token.trim(),
},
user: res.data.user,
courses: res.data.courses,
courses: res.data.courses ?? [],
};
accountStore.getState().upsertAccount(nextAccount);
await loadCourses(nextAccount.id, nextAccount.status);
accountStore.getState().setSelectedCourseId(null);
accountStore.getState().setRecords([]);
setShowDialog(false);
@@ -294,6 +324,50 @@ const Account = () => {
accountStore.getState().setExpandedAccountId(nextId);
};
const loadCourses = async (
accountId = selectedAccount()?.id,
status = courseKind(),
) => {
if (!accountId) {
return;
}
setIsRefreshingCourses(true);
setErrorMessage("");
try {
const res = await getAccountClient(accountId).courseApi({ status });
const courses = res.data.courses ?? [];
const account = accountStore
.getState()
.accounts.find((item) => item.id === accountId);
accountStore.getState().setAccountCourses(accountId, courses);
const currentCourseId = accountStore.getState().selectedCourseId;
if (account?.id === accountStore.getState().selectedAccountId) {
const hasSelectedCourse = courses.some(
(item) => item.id === currentCourseId,
);
if (!hasSelectedCourse) {
accountStore.getState().setSelectedCourseId(courses[0]?.id ?? null);
accountStore.getState().setRecords([]);
}
}
} catch (error) {
const message =
error instanceof Error ? error.message : "获取课程失败,请稍后重试。";
setErrorMessage(message);
accountStore.getState().setAccountCourses(accountId, []);
if (accountStore.getState().selectedAccountId === accountId) {
accountStore.getState().setSelectedCourseId(null);
accountStore.getState().setRecords([]);
}
} finally {
setIsRefreshingCourses(false);
}
};
const handleRefreshAccount = async () => {
const account = selectedAccount();
if (!account) {
@@ -317,8 +391,9 @@ const Account = () => {
...account,
sessionId: res.data.session_id,
user: res.data.user,
courses: res.data.courses,
courses: res.data.courses ?? account.courses,
});
await loadCourses(account.id, account.status);
} catch (error) {
const message =
error instanceof Error ? error.message : "刷新账号失败,请稍后重试。";
@@ -374,6 +449,12 @@ const Account = () => {
record_type: nextRecordType,
});
accountStore.getState().setRecords(res.data.list);
accountStore
.getState()
.setRecordCache(
createRecordCacheKey(account.id, courseId, nextRecordType),
res.data.list,
);
} catch (error) {
const message =
error instanceof Error ? error.message : "获取记录失败,请稍后重试。";
@@ -385,9 +466,8 @@ const Account = () => {
}
};
const handleSelectCourse = async (courseId: number) => {
const handleSelectCourse = (courseId: number) => {
accountStore.getState().setSelectedCourseId(courseId);
await loadCourseRecords(courseId);
};
const handleRefreshRecords = async () => {
@@ -448,6 +528,8 @@ const Account = () => {
client: getAccountClient(account.id),
isRunningStudy: () =>
!!accountStore.getState().runningStudyMap[account.id],
setIsRunningStudy: () =>
accountStore.getState().setAccountRunningStudy(account.id, false),
onLog: (message: string, accoundID: string) =>
appendStudyLog(message, accoundID),
});
@@ -474,15 +556,46 @@ const Account = () => {
appendStudyLog(`已发送停止刷课指令:${account.user.name}`, account.id);
};
createEffect(
on([selectedAccountId, courseKind], ([accountId, kind]) => {
if (!accountId) {
return;
}
void loadCourses(accountId, kind);
}),
);
createEffect(
on(
[selectedAccountId, selectedCourseId, recordType],
([accountId, courseId, type]) => {
if (!accountId || !courseId) {
accountStore.getState().setRecords([]);
return;
}
const cachedRecords =
recordCacheMap()[createRecordCacheKey(accountId, courseId, type)] ??
[];
accountStore.getState().setRecords(cachedRecords);
},
),
);
createEffect(() => {
const courseId = selectedCourseId();
const account = selectedAccount();
const type = recordType();
const cacheKey = account
? createRecordCacheKey(account.id, courseId ?? 0, type)
: "";
const hasCachedRecords = cacheKey ? cacheKey in recordCacheMap() : false;
if (!hasRestoredRecords()) {
setHasRestoredRecords(true);
if (courseId && account && records().length > 0) {
if (courseId && account && (records().length > 0 || hasCachedRecords)) {
return;
}
}
@@ -491,24 +604,30 @@ const Account = () => {
return;
}
if (hasCachedRecords) {
return;
}
void loadCourseRecords(courseId, type);
});
return (
<div class="flex h-full min-h-0 flex-col overflow-hidden">
<div class="flex shrink-0 items-center justify-between gap-4 rounded-[28px] border border-white/80 bg-[linear-gradient(135deg,rgba(236,254,255,0.95),rgba(255,255,255,0.92))] px-5 py-4 shadow-[0_18px_45px_-28px_rgba(8,145,178,0.35)]">
<div class="flex shrink-0 items-center justify-between gap-3 rounded-[24px] border border-white/80 bg-[linear-gradient(135deg,rgba(236,254,255,0.95),rgba(255,255,255,0.92))] px-4 py-3 shadow-[0_18px_45px_-28px_rgba(8,145,178,0.35)]">
<div>
<p class="text-xs font-medium tracking-[0.26em] text-cyan-700/75 uppercase">
Account Center
</p>
<h1 class="mt-2 text-2xl font-semibold text-zinc-900"></h1>
<p class="mt-1 text-sm text-zinc-500">
<h1 class="mt-1 text-xl font-semibold text-zinc-900 sm:text-2xl">
</h1>
<p class="mt-0.5 text-xs text-zinc-500 sm:text-sm">
</p>
</div>
<button
class="rounded-2xl bg-[linear-gradient(135deg,#06b6d4,#14b8a6)] px-4 py-3 text-sm font-medium text-white shadow-lg shadow-cyan-500/20 transition hover:-translate-y-px hover:shadow-cyan-500/30"
class="rounded-xl bg-[linear-gradient(135deg,#06b6d4,#14b8a6)] px-3.5 py-2.5 text-sm font-medium text-white shadow-lg shadow-cyan-500/20 transition hover:-translate-y-px hover:shadow-cyan-500/30"
onClick={openDialog}
>
@@ -521,6 +640,7 @@ const Account = () => {
selectedAccountId={selectedAccountId()}
expandedAccountId={expandedAccountId()}
statusOptions={statusOptions}
currentCourseKind={courseKind()}
hostLabels={hostLabels()}
isRefreshingAccount={isRefreshingAccount()}
loggingOutId={loggingOutId()}
@@ -538,12 +658,17 @@ const Account = () => {
selectedCourseId={selectedCourseId()}
selectedCourse={selectedCourse()}
recordType={recordType()}
courseKind={courseKind()}
currentCourseKindLabel={currentCourseKindLabel()}
showingCachedRecords={showingCachedRecords()}
recordTypeOptions={recordTypeOptions}
courseRecordTypeOptions={statusOptions}
records={records()}
studyLogs={studyLogs()}
recordsLoading={recordsLoading()}
recordError={recordError()}
isRefreshingRecords={isRefreshingRecords()}
isRefreshingCourseRecords={isRefreshingCourses()}
isRunningStudy={isRunningStudy()}
isRefreshingLogs={isRefreshingLogs()}
autoScrollLogs={settingsState().autoScrollLogs}
@@ -552,9 +677,15 @@ const Account = () => {
logFontSize={settingsState().logFontSize}
onSelectCourse={(courseId) => void handleSelectCourse(courseId)}
onRefreshRecords={() => void handleRefreshRecords()}
onRefreshCourseRecords={() => void loadCourses()}
onChangeRecordType={(value) =>
accountStore.getState().setRecordType(value)
}
onChangeCourseRecordType={(value) => {
accountStore.getState().setCourseKind(value);
accountStore.getState().setSelectedCourseId(null);
accountStore.getState().setRecords([]);
}}
onStartStudy={() => void handleStartStudy()}
onStopStudy={handleStopStudy}
onRefreshLogs={handleRefreshLogs}

View File

@@ -65,7 +65,7 @@ const Setting = () => {
return (
<div class="flex h-full min-h-0 flex-col overflow-hidden">
<div class="flex shrink-0 items-center justify-between gap-4 rounded-[28px] border border-white/80 bg-[linear-gradient(135deg,_rgba(255,255,255,0.92),_rgba(240,249,255,0.96))] px-5 py-4 shadow-[0_18px_45px_-28px_rgba(15,23,42,0.18)]">
<div class="flex shrink-0 items-center justify-between gap-4 rounded-[28px] border border-white/80 bg-[linear-gradient(135deg,rgba(255,255,255,0.92),rgba(240,249,255,0.96))] px-5 py-4 shadow-[0_18px_45px_-28px_rgba(15,23,42,0.18)]">
<div>
<p class="text-xs font-medium tracking-[0.26em] text-cyan-700/75 uppercase">
Settings Center