feat(release): bump version to 0.1.2
## 详细信息 - 升级项目版本号到 0.1.2 - 增强刷课稳定性(失败重试、心跳检测、状态自动纠正) - 优化账号页与工作台紧凑布局,增加已学/未学区分与记录筛选 - 新增首次进入更新检查、更新日志弹窗与在线下载/回退 Release 跳转
This commit is contained in:
@@ -10,6 +10,17 @@ import {
|
||||
import { accountStore } from "~/store/account";
|
||||
import { settingsStore } from "~/store/settings";
|
||||
|
||||
type LogRow = {
|
||||
id: string;
|
||||
accountId: string;
|
||||
accountName: string;
|
||||
host: string;
|
||||
seq: number;
|
||||
timestamp: string;
|
||||
timestampValue: number;
|
||||
content: string;
|
||||
};
|
||||
|
||||
const extractTimestamp = (message: string) => {
|
||||
const match = message.match(/^\[(\d{2}:\d{2}:\d{2})\]\s*/);
|
||||
return match?.[1] ?? null;
|
||||
@@ -25,7 +36,6 @@ const parseTimestampValue = (timestamp: string | null) => {
|
||||
}
|
||||
|
||||
const [hours, minutes, seconds] = timestamp.split(":").map(Number);
|
||||
|
||||
if ([hours, minutes, seconds].some((item) => Number.isNaN(item))) {
|
||||
return -1;
|
||||
}
|
||||
@@ -33,6 +43,11 @@ const parseTimestampValue = (timestamp: string | null) => {
|
||||
return hours * 3600 + minutes * 60 + seconds;
|
||||
};
|
||||
|
||||
const normalizeContent = (message: string) => {
|
||||
const value = stripTimestamp(message).replace(/\s+/g, " ").trim();
|
||||
return value || "-";
|
||||
};
|
||||
|
||||
const Logs = () => {
|
||||
const [accountState, setAccountState] = createSignal(accountStore.getState());
|
||||
const [settingsState, setSettingsState] = createSignal(
|
||||
@@ -66,75 +81,60 @@ const Logs = () => {
|
||||
) as Record<string, { name: string; host: string }>;
|
||||
});
|
||||
|
||||
const allLogs = createMemo(() => {
|
||||
return Object.entries(accountState().studyLogsMap)
|
||||
.flatMap(([accountId, messages]) => {
|
||||
const logRows = createMemo<LogRow[]>(() => {
|
||||
const rows = Object.entries(accountState().studyLogsMap).flatMap(
|
||||
([accountId, messages]) => {
|
||||
const accountInfo = accountInfoMap()[accountId];
|
||||
const accountName = accountInfo?.name ?? accountId;
|
||||
|
||||
return messages.map((message, index) => {
|
||||
const timestamp = extractTimestamp(message);
|
||||
|
||||
return {
|
||||
id: `${accountId}-${index}`,
|
||||
accountId,
|
||||
accountName,
|
||||
index: index + 1,
|
||||
timestamp,
|
||||
accountName: accountInfo?.name ?? accountId,
|
||||
host: accountInfo?.host ?? "-",
|
||||
seq: index + 1,
|
||||
timestamp: timestamp ?? "--:--:--",
|
||||
timestampValue: parseTimestampValue(timestamp),
|
||||
content: stripTimestamp(message),
|
||||
raw: message,
|
||||
content: normalizeContent(message),
|
||||
};
|
||||
});
|
||||
})
|
||||
.sort((left, right) => {
|
||||
if (left.timestampValue !== right.timestampValue) {
|
||||
return left.timestampValue - right.timestampValue;
|
||||
}
|
||||
|
||||
if (left.index !== right.index) {
|
||||
return left.index - right.index;
|
||||
}
|
||||
|
||||
return left.accountId.localeCompare(right.accountId);
|
||||
});
|
||||
});
|
||||
|
||||
const accountSummaries = createMemo(() => {
|
||||
const accountIds = Array.from(
|
||||
new Set([
|
||||
...accountState().accounts.map((account) => account.id),
|
||||
...Object.keys(accountState().studyLogsMap),
|
||||
]),
|
||||
},
|
||||
);
|
||||
|
||||
return accountIds.map((accountId) => {
|
||||
const accountInfo = accountInfoMap()[accountId];
|
||||
const logs = accountState().studyLogsMap[accountId] ?? [];
|
||||
const latestMessage = logs[logs.length - 1] ?? "暂无日志";
|
||||
return rows.sort((left, right) => {
|
||||
const leftTs =
|
||||
left.timestampValue >= 0 ? left.timestampValue : Number.MAX_SAFE_INTEGER;
|
||||
const rightTs =
|
||||
right.timestampValue >= 0
|
||||
? right.timestampValue
|
||||
: Number.MAX_SAFE_INTEGER;
|
||||
|
||||
return {
|
||||
id: accountId,
|
||||
name: accountInfo?.name ?? accountId,
|
||||
host: accountInfo?.host ?? "未知账号来源",
|
||||
total: logs.length,
|
||||
latestMessage: stripTimestamp(latestMessage),
|
||||
latestTime: extractTimestamp(latestMessage),
|
||||
};
|
||||
if (leftTs !== rightTs) {
|
||||
return leftTs - rightTs;
|
||||
}
|
||||
if (left.accountName !== right.accountName) {
|
||||
return left.accountName.localeCompare(right.accountName, "zh-CN");
|
||||
}
|
||||
if (left.seq !== right.seq) {
|
||||
return left.seq - right.seq;
|
||||
}
|
||||
return left.accountId.localeCompare(right.accountId);
|
||||
});
|
||||
});
|
||||
|
||||
const latestLog = createMemo(() => {
|
||||
const logs = allLogs();
|
||||
return logs.length > 0 ? logs[logs.length - 1] : null;
|
||||
const totalAccountsWithLogs = createMemo(() => {
|
||||
return Object.values(accountState().studyLogsMap).filter(
|
||||
(messages) => messages.length > 0,
|
||||
).length;
|
||||
});
|
||||
|
||||
const totalAccountsWithLogs = createMemo(() => {
|
||||
return accountSummaries().filter((item) => item.total > 0).length;
|
||||
const latestLog = createMemo(() => {
|
||||
const rows = logRows();
|
||||
return rows.length > 0 ? rows[rows.length - 1] : null;
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
allLogs().length;
|
||||
logRows().length;
|
||||
|
||||
if (!settingsState().autoScrollLogs) {
|
||||
return;
|
||||
@@ -170,7 +170,7 @@ const Logs = () => {
|
||||
日志中心
|
||||
</h1>
|
||||
<p class="mt-1 text-xs leading-5 text-zinc-600 sm:text-sm">
|
||||
聚合全部账号日志,优先把空间留给正文。
|
||||
单行并列展示时间、账号、姓名与日志内容。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -180,7 +180,7 @@ const Logs = () => {
|
||||
TOTAL LOGS
|
||||
</span>
|
||||
<span class="text-sm font-semibold text-zinc-950">
|
||||
{allLogs().length}
|
||||
{logRows().length}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 rounded-full border border-white/80 bg-white/85 px-3 py-1.5 shadow-sm">
|
||||
@@ -191,14 +191,11 @@ const Logs = () => {
|
||||
{totalAccountsWithLogs()}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 rounded-full border border-white/80 bg-white/85 px-3 py-1.5 shadow-sm">
|
||||
<span class="text-[10px] tracking-[0.18em] text-zinc-400 uppercase">
|
||||
AUTO SCROLL
|
||||
</span>
|
||||
<span class="text-sm font-medium text-zinc-900">
|
||||
{settingsState().autoScrollLogs ? "跟随最新" : "手动查看"}
|
||||
</span>
|
||||
</div>
|
||||
<Show when={latestLog()}>
|
||||
<div class="rounded-full border border-white/80 bg-white/85 px-3 py-1.5 text-xs text-zinc-600 shadow-sm">
|
||||
最新: {latestLog()?.timestamp} / {latestLog()?.accountName}
|
||||
</div>
|
||||
</Show>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-rose-200 bg-[linear-gradient(135deg,rgba(255,241,242,0.95),rgba(255,255,255,0.9))] px-3.5 py-1.5 text-sm font-medium text-rose-600 transition hover:-translate-y-px hover:bg-rose-50"
|
||||
@@ -210,120 +207,73 @@ const Logs = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-3 py-3">
|
||||
<section class="flex min-h-0 flex-col overflow-hidden rounded-[26px] border border-white/80 bg-[linear-gradient(180deg,rgba(255,255,255,0.92),rgba(248,250,252,0.9))] shadow-[0_18px_48px_-30px_rgba(15,23,42,0.24)]">
|
||||
<div class="shrink-0 border-b border-zinc-200/80 px-4 py-3">
|
||||
<p class="text-base font-semibold text-zinc-950">账号概览</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">最新输出与日志数量</p>
|
||||
<section class="mt-3 flex min-h-0 flex-1 flex-col overflow-hidden rounded-[26px] border border-zinc-200/80 bg-[linear-gradient(180deg,rgba(11,18,32,0.98),rgba(10,10,16,1))] shadow-[0_24px_60px_-32px_rgba(15,23,42,0.45)]">
|
||||
<div class="shrink-0 border-b border-white/10 px-4 py-3">
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span class="rounded-full border border-emerald-400/20 bg-emerald-400/10 px-3 py-1 text-emerald-200">
|
||||
{settingsState().showLogTimestamps ? "显示时间" : "隐藏时间"}
|
||||
</span>
|
||||
<span class="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-slate-300">
|
||||
{settingsState().autoScrollLogs ? "自动滚动中" : "自动滚动关闭"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-0 shrink-0 flex-row gap-3 space-y-2.5 overflow-y-auto px-3 py-3">
|
||||
<For each={accountSummaries()}>
|
||||
{(account) => (
|
||||
<div class="rounded-2xl border border-zinc-200 bg-white p-4 transition hover:shadow-md">
|
||||
{/* 顶部 */}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-semibold text-zinc-900">
|
||||
{account.name}
|
||||
</p>
|
||||
<p class="text-xs text-zinc-400">{account.host}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-full bg-amber-100 px-2.5 py-1 text-xs text-amber-700">
|
||||
{account.total}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 最新日志 */}
|
||||
<div class="mt-3 rounded-xl bg-zinc-50 p-3">
|
||||
<div class="flex justify-between text-[11px] text-zinc-400">
|
||||
<span>Latest</span>
|
||||
<span>{account.latestTime ?? "--:--"}</span>
|
||||
</div>
|
||||
|
||||
<p class="mt-1 line-clamp-2 text-xs text-zinc-600">
|
||||
{account.latestMessage}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[26px] border border-zinc-200/80 bg-[linear-gradient(180deg,rgba(11,18,32,0.98),rgba(10,10,16,1))] shadow-[0_24px_60px_-32px_rgba(15,23,42,0.45)]">
|
||||
<div class="shrink-0 border-b border-white/10 px-4 py-3">
|
||||
<div class="flex flex-row items-center justify-between gap-2">
|
||||
<div>
|
||||
<p class="text-base font-semibold text-slate-400">实时日志流</p>
|
||||
<p class="mt-1 text-xs text-slate-400">
|
||||
保留关键信息,把更多高度让给正文
|
||||
</p>
|
||||
<div
|
||||
ref={logContainerRef}
|
||||
class="min-h-0 flex-1 overflow-auto px-3 pt-0 pb-3 font-mono text-emerald-200"
|
||||
>
|
||||
<Show
|
||||
when={logRows().length > 0}
|
||||
fallback={
|
||||
<div class="flex h-full items-center justify-center rounded-[26px] border border-dashed border-white/10 bg-white/3 px-6 text-slate-500">
|
||||
暂无日志输出
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span class="rounded-full border border-emerald-400/20 bg-emerald-400/10 px-3 py-1 text-emerald-200">
|
||||
{settingsState().showLogTimestamps ? "显示时间" : "隐藏时间"}
|
||||
</span>
|
||||
<span class="rounded-full border border-white/10 bg-white/5 px-3 py-1 text-slate-300">
|
||||
{settingsState().autoScrollLogs
|
||||
? "自动滚动中"
|
||||
: "自动滚动关闭"}
|
||||
</span>
|
||||
<Show when={latestLog()}>
|
||||
<span class="rounded-full border border-amber-400/20 bg-amber-400/10 px-3 py-1 text-amber-200">
|
||||
最近来源 {latestLog()?.accountName}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={logContainerRef}
|
||||
class="min-h-0 flex-1 overflow-y-auto px-4 py-3 font-mono text-emerald-200"
|
||||
}
|
||||
>
|
||||
<Show
|
||||
when={allLogs().length > 0}
|
||||
fallback={
|
||||
<div class="flex h-full items-center justify-center rounded-[26px] border border-dashed border-white/10 bg-white/3 px-6 text-slate-500">
|
||||
暂无日志输出
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<For each={allLogs()}>
|
||||
{(log) => (
|
||||
<div class="rounded-lg px-3 py-2 transition hover:bg-white/4">
|
||||
{/* 头部 */}
|
||||
<div class="flex items-center justify-between text-[11px] text-slate-400">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-amber-300">{log.accountName}</span>
|
||||
<span>#{log.index}</span>
|
||||
<div class="min-w-[980px]">
|
||||
<div class="sticky top-0 z-20 grid grid-cols-[100px_140px_220px_180px_80px_minmax(320px,1fr)] gap-3 border-b border-white/10 bg-zinc-950 px-3 py-2 text-[11px] tracking-wide text-slate-300 uppercase shadow-[0_8px_16px_-12px_rgba(0,0,0,0.8)]">
|
||||
<span>时间</span>
|
||||
<span>姓名</span>
|
||||
<span>账号</span>
|
||||
<span>主机</span>
|
||||
<span>序号</span>
|
||||
<span>内容</span>
|
||||
</div>
|
||||
|
||||
<Show when={settingsState().showLogTimestamps}>
|
||||
<span class="text-emerald-300/80">
|
||||
{log.timestamp}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<span class="text-slate-500">{log.accountId}</span>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<p class="mt-1 text-sm leading-5 whitespace-pre-wrap text-emerald-200">
|
||||
{log.content}
|
||||
</p>
|
||||
<div class="relative z-0">
|
||||
<For each={logRows()}>
|
||||
{(row) => (
|
||||
<div class="grid grid-cols-[100px_140px_220px_180px_80px_minmax(320px,1fr)] items-center gap-3 border-b border-white/5 px-3 py-2 hover:bg-white/4">
|
||||
<span class="truncate text-emerald-300">
|
||||
{settingsState().showLogTimestamps
|
||||
? row.timestamp
|
||||
: "--:--:--"}
|
||||
</span>
|
||||
<span
|
||||
class="truncate text-amber-200"
|
||||
title={row.accountName}
|
||||
>
|
||||
{row.accountName}
|
||||
</span>
|
||||
<span class="truncate text-slate-300" title={row.accountId}>
|
||||
{row.accountId}
|
||||
</span>
|
||||
<span class="truncate text-slate-400" title={row.host}>
|
||||
{row.host}
|
||||
</span>
|
||||
<span class="truncate text-cyan-200">#{row.seq}</span>
|
||||
<span class="truncate text-emerald-100" title={row.content}>
|
||||
{row.content}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user