feat(release): bump version to 0.1.2

## 详细信息
- 升级项目版本号到 0.1.2
- 增强刷课稳定性(失败重试、心跳检测、状态自动纠正)
- 优化账号页与工作台紧凑布局,增加已学/未学区分与记录筛选
- 新增首次进入更新检查、更新日志弹窗与在线下载/回退 Release 跳转
This commit is contained in:
2026-04-02 22:33:04 +08:00
parent a061123e36
commit 58555c5043
12 changed files with 1569 additions and 413 deletions

View File

@@ -19,6 +19,7 @@ const withLogTimestamp = (message: string) => {
return `[${timestamp}] ${message}`;
};
const MAX_STUDY_LOGS_PER_ACCOUNT = 1000;
export type AccountAuth = {
password: string;
@@ -38,6 +39,45 @@ export type AccountItem = {
export type RecordCacheMap = Record<string, RecordItem[]>;
type PersistPreferences = {
persistAccounts: boolean;
persistRecords: boolean;
persistLogs: boolean;
};
const settingsStorageKey = "settings-storage";
const defaultPersistPreferences: PersistPreferences = {
persistAccounts: true,
persistRecords: true,
persistLogs: true,
};
const resolvePersistPreferences = (): PersistPreferences => {
try {
const rawValue = localStorage.getItem(settingsStorageKey);
if (!rawValue) {
return defaultPersistPreferences;
}
const parsedValue = JSON.parse(rawValue) as {
state?: Partial<PersistPreferences>;
};
const persistedState = parsedValue?.state;
return {
persistAccounts:
persistedState?.persistAccounts ??
defaultPersistPreferences.persistAccounts,
persistRecords:
persistedState?.persistRecords ?? defaultPersistPreferences.persistRecords,
persistLogs:
persistedState?.persistLogs ?? defaultPersistPreferences.persistLogs,
};
} catch {
return defaultPersistPreferences;
}
};
type AccountState = {
accounts: AccountItem[];
selectedAccountId: string;
@@ -49,6 +89,7 @@ type AccountState = {
recordCacheMap: RecordCacheMap;
studyLogsMap: Record<string, string[]>;
runningStudyMap: Record<string, boolean>;
studyHeartbeatMap: Record<string, number>;
setSelectedAccountId: (accountId: string) => void;
setExpandedAccountId: (accountId: string) => void;
setSelectedCourseId: (courseId: number | null) => void;
@@ -57,6 +98,9 @@ type AccountState = {
setRecords: (records: RecordItem[]) => void;
setRecordCache: (cacheKey: string, records: RecordItem[]) => void;
setAccountRunningStudy: (accountId: string, value: boolean) => void;
touchStudyHeartbeat: (accountId: string, timestamp?: number) => void;
clearStudyHeartbeat: (accountId: string) => void;
clearAllStudyHeartbeat: () => void;
appendStudyLog: (accountId: string, message: string) => void;
clearStudyLogs: (accountId: string) => void;
clearAllStudyLogs: () => void;
@@ -78,6 +122,7 @@ export const accountStore = createStore<AccountState>()(
recordCacheMap: {},
studyLogsMap: {},
runningStudyMap: {},
studyHeartbeatMap: {},
setSelectedAccountId: (accountId) =>
set({ selectedAccountId: accountId }),
setExpandedAccountId: (accountId) =>
@@ -100,16 +145,39 @@ export const accountStore = createStore<AccountState>()(
[accountId]: value,
},
})),
appendStudyLog: (accountId, message) =>
touchStudyHeartbeat: (accountId, timestamp) =>
set((state) => ({
studyLogsMap: {
...state.studyLogsMap,
[accountId]: [
...(state.studyLogsMap[accountId] ?? []),
withLogTimestamp(message),
],
studyHeartbeatMap: {
...state.studyHeartbeatMap,
[accountId]: timestamp ?? Date.now(),
},
})),
clearStudyHeartbeat: (accountId) =>
set((state) => ({
studyHeartbeatMap: Object.fromEntries(
Object.entries(state.studyHeartbeatMap).filter(
([key]) => key !== accountId,
),
),
})),
clearAllStudyHeartbeat: () =>
set({
studyHeartbeatMap: {},
}),
appendStudyLog: (accountId, message) =>
set((state) => {
const nextLogs = [
...(state.studyLogsMap[accountId] ?? []),
withLogTimestamp(message),
].slice(-MAX_STUDY_LOGS_PER_ACCOUNT);
return {
studyLogsMap: {
...state.studyLogsMap,
[accountId]: nextLogs,
},
};
}),
clearStudyLogs: (accountId) =>
set((state) => ({
studyLogsMap: {
@@ -171,24 +239,42 @@ export const accountStore = createStore<AccountState>()(
([key]) => key !== accountId,
),
),
studyHeartbeatMap: Object.fromEntries(
Object.entries(state.studyHeartbeatMap).filter(
([key]) => key !== accountId,
),
),
};
}),
}),
{
name: "account-storage",
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
accounts: state.accounts,
selectedAccountId: state.selectedAccountId,
expandedAccountId: state.expandedAccountId,
selectedCourseId: state.selectedCourseId,
courseKind: state.courseKind,
recordType: state.recordType,
records: state.records,
recordCacheMap: state.recordCacheMap,
studyLogsMap: state.studyLogsMap,
runningStudyMap: state.runningStudyMap,
}),
partialize: (state) => {
const preferences = resolvePersistPreferences();
const persistedState: Partial<AccountState> = {
courseKind: state.courseKind,
recordType: state.recordType,
};
if (preferences.persistAccounts) {
persistedState.accounts = state.accounts;
persistedState.selectedAccountId = state.selectedAccountId;
persistedState.expandedAccountId = state.expandedAccountId;
persistedState.selectedCourseId = state.selectedCourseId;
}
if (preferences.persistRecords) {
persistedState.records = state.records;
persistedState.recordCacheMap = state.recordCacheMap;
}
if (preferences.persistLogs) {
persistedState.studyLogsMap = state.studyLogsMap;
}
return persistedState;
},
},
),
);