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

@@ -13,9 +13,11 @@ import AddAccountDialog, {
import CourseWorkspace from "~/components/account/CourseWorkspace";
import {
createWkClient,
hostApi,
loginApi,
runStudyQueue,
type CourseKind,
type RecordItem,
type RecordType,
} from "~/service/wk";
import { setUnauthorizedHandler } from "~/service/http";
@@ -47,13 +49,32 @@ const createDefaultForm = (host: string): LoginForm => ({
const stripHtml = (value: string) => value.replace(/<[^>]+>/g, "").trim();
const parseDurationToSeconds = (value: string) => {
const parts = value.split(":").map(Number);
if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
const input = value.trim();
if (!input) {
return 0;
}
const [hours, minutes, seconds] = parts;
return hours * 3600 + minutes * 60 + seconds;
const numeric = Number(input);
if (!Number.isNaN(numeric)) {
return Math.max(0, Math.floor(numeric));
}
const parts = input.split(":").map(Number);
if (parts.some((part) => Number.isNaN(part))) {
return 0;
}
if (parts.length === 3) {
const [hours, minutes, seconds] = parts;
return hours * 3600 + minutes * 60 + seconds;
}
if (parts.length === 2) {
const [minutes, seconds] = parts;
return minutes * 60 + seconds;
}
return 0;
};
const createRecordCacheKey = (
@@ -61,6 +82,8 @@ const createRecordCacheKey = (
courseId: number,
recordType: RecordType,
) => `${accountId}::${courseId}::${recordType || "course"}`;
const STUDY_HEARTBEAT_CHECK_INTERVAL_MS = 5000;
const STUDY_HEARTBEAT_TIMEOUT_MS = 45000;
const Account = () => {
const [storeState, setStoreState] = createSignal(accountStore.getState());
@@ -80,8 +103,67 @@ const Account = () => {
const [isRefreshingRecords, setIsRefreshingRecords] = createSignal(false);
const [isRefreshingCourses, setIsRefreshingCourses] = createSignal(false);
const [isRefreshingLogs, setIsRefreshingLogs] = createSignal(false);
const [isLoadingRemoteHosts, setIsLoadingRemoteHosts] = createSignal(false);
const [hasRestoredRecords, setHasRestoredRecords] = createSignal(false);
const accountClients = new Map<string, ReturnType<typeof createWkClient>>();
let recordRequestToken = 0;
const touchStudyHeartbeat = (accountId: string) => {
accountStore.getState().touchStudyHeartbeat(accountId);
};
const clearStudyHeartbeat = (accountId: string) => {
accountStore.getState().clearStudyHeartbeat(accountId);
};
const runStudyHeartbeatWatchdog = () => {
const now = Date.now();
const snapshot = accountStore.getState();
for (const [accountId, running] of Object.entries(snapshot.runningStudyMap)) {
if (!running) {
continue;
}
const lastHeartbeat = snapshot.studyHeartbeatMap[accountId] ?? 0;
if (now - lastHeartbeat <= STUDY_HEARTBEAT_TIMEOUT_MS) {
continue;
}
snapshot.setAccountRunningStudy(accountId, false);
snapshot.clearStudyHeartbeat(accountId);
snapshot.appendStudyLog(
accountId,
`检测到刷课任务超时(超过 ${Math.floor(
STUDY_HEARTBEAT_TIMEOUT_MS / 1000,
)} 秒无活动),已自动重置状态`,
);
}
};
const loadRemoteHostsIfNeeded = async () => {
if (isLoadingRemoteHosts()) {
return;
}
if (settingsStore.getState().remoteHosts.length > 0) {
return;
}
setIsLoadingRemoteHosts(true);
try {
const res = await hostApi();
settingsStore.getState().setRemoteHosts(
res.data.list.map((item) => ({
label: item.name,
host: item.host,
})),
);
} catch {
// Ignore host bootstrap errors in account page to avoid blocking main flow.
} finally {
setIsLoadingRemoteHosts(false);
}
};
onMount(() => {
const unsubscribeAccount = accountStore.subscribe((state) => {
@@ -90,12 +172,19 @@ const Account = () => {
const unsubscribeSettings = settingsStore.subscribe((state) => {
setSettingsState(state);
});
void loadRemoteHostsIfNeeded();
setUnauthorizedHandler(reloginBySession);
runStudyHeartbeatWatchdog();
const heartbeatTimer = window.setInterval(
runStudyHeartbeatWatchdog,
STUDY_HEARTBEAT_CHECK_INTERVAL_MS,
);
onCleanup(() => {
unsubscribeAccount();
unsubscribeSettings();
setUnauthorizedHandler(null);
window.clearInterval(heartbeatTimer);
});
});
@@ -189,6 +278,12 @@ const Account = () => {
accountStore.getState().appendStudyLog(targetAccountId, message);
};
const cancelPendingRecordRequest = () => {
recordRequestToken += 1;
setRecordsLoading(false);
setIsRefreshingRecords(false);
};
const getAccountClient = (accountId: string) => {
const existingClient = accountClients.get(accountId);
if (existingClient) {
@@ -313,6 +408,7 @@ const Account = () => {
};
const handleSelectAccount = (accountId: string) => {
cancelPendingRecordRequest();
accountStore.getState().setSelectedAccountId(accountId);
accountStore.getState().setSelectedCourseId(null);
accountStore.getState().setRecords([]);
@@ -360,6 +456,7 @@ const Account = () => {
setErrorMessage(message);
accountStore.getState().setAccountCourses(accountId, []);
if (accountStore.getState().selectedAccountId === accountId) {
cancelPendingRecordRequest();
accountStore.getState().setSelectedCourseId(null);
accountStore.getState().setRecords([]);
}
@@ -437,36 +534,59 @@ const Account = () => {
if (!account) {
return;
}
const requestToken = ++recordRequestToken;
const accountId = account.id;
setRecordsLoading(true);
setIsRefreshingRecords(true);
setRecordError("");
try {
const res = await getAccountClient(account.id).recordApi({
const res = await getAccountClient(accountId).recordApi({
course_id: String(courseId),
page: 0,
record_type: nextRecordType,
});
accountStore.getState().setRecords(res.data.list);
if (requestToken !== recordRequestToken) {
return;
}
const rawList = (res as { data?: { list?: unknown } })?.data?.list;
const list = (Array.isArray(rawList) ? rawList : []) as RecordItem[];
const snapshot = accountStore.getState();
if (
snapshot.selectedAccountId !== accountId ||
snapshot.selectedCourseId !== courseId ||
snapshot.recordType !== nextRecordType
) {
return;
}
accountStore.getState().setRecords(list);
accountStore
.getState()
.setRecordCache(
createRecordCacheKey(account.id, courseId, nextRecordType),
res.data.list,
createRecordCacheKey(accountId, courseId, nextRecordType),
list,
);
} catch (error) {
if (requestToken !== recordRequestToken) {
return;
}
const message =
error instanceof Error ? error.message : "获取记录失败,请稍后重试。";
setRecordError(message);
accountStore.getState().setRecords([]);
} finally {
setRecordsLoading(false);
setIsRefreshingRecords(false);
if (requestToken === recordRequestToken) {
setRecordsLoading(false);
setIsRefreshingRecords(false);
}
}
};
const handleSelectCourse = (courseId: number) => {
cancelPendingRecordRequest();
accountStore.getState().setSelectedCourseId(courseId);
};
@@ -507,18 +627,19 @@ const Account = () => {
}
const queueItems = records()
.filter((item) => item.progress !== "1.00")
.map((item) => ({
nodeId: item.id,
name: item.name,
currentTime: Number(item.duration || 0),
currentTime: parseDurationToSeconds(item.duration),
totalTime: parseDurationToSeconds(item.videoDuration),
progress: item.progress,
completed: stripHtml(item.state) === "已学",
}));
}))
.filter((item) => item.progress !== "1.00" && !item.completed);
try {
accountStore.getState().setAccountRunningStudy(account.id, true);
touchStudyHeartbeat(account.id);
appendStudyLog(`开始刷课:${course.name}`, account.id);
await runStudyQueue({
accountId: account.id,
@@ -528,10 +649,14 @@ 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),
setIsRunningStudy: () => {
accountStore.getState().setAccountRunningStudy(account.id, false);
clearStudyHeartbeat(account.id);
},
onLog: (message: string, accoundID: string) => {
touchStudyHeartbeat(accoundID);
appendStudyLog(message, accoundID);
},
});
if (accountStore.getState().runningStudyMap[account.id]) {
appendStudyLog(`刷课完成:${course.name}`, account.id);
@@ -543,6 +668,7 @@ const Account = () => {
setRecordError(message);
} finally {
accountStore.getState().setAccountRunningStudy(account.id, false);
clearStudyHeartbeat(account.id);
}
};
@@ -553,12 +679,14 @@ const Account = () => {
}
accountStore.getState().setAccountRunningStudy(account.id, false);
clearStudyHeartbeat(account.id);
appendStudyLog(`已发送停止刷课指令:${account.user.name}`, account.id);
};
createEffect(
on([selectedAccountId, courseKind], ([accountId, kind]) => {
if (!accountId) {
cancelPendingRecordRequest();
return;
}
@@ -570,6 +698,7 @@ const Account = () => {
on(
[selectedAccountId, selectedCourseId, recordType],
([accountId, courseId, type]) => {
cancelPendingRecordRequest();
if (!accountId || !courseId) {
accountStore.getState().setRecords([]);
return;
@@ -613,12 +742,12 @@ const Account = () => {
return (
<div class="flex h-full min-h-0 flex-col overflow-hidden">
<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 class="flex shrink-0 items-center justify-between gap-2.5 rounded-[22px] border border-white/80 bg-[linear-gradient(135deg,rgba(236,254,255,0.95),rgba(255,255,255,0.92))] px-3.5 py-2.5 shadow-[0_16px_40px_-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-1 text-xl font-semibold text-zinc-900 sm:text-2xl">
<h1 class="mt-1 text-lg font-semibold text-zinc-900 sm:text-xl">
</h1>
<p class="mt-0.5 text-xs text-zinc-500 sm:text-sm">
@@ -627,14 +756,14 @@ const Account = () => {
</div>
<button
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"
class="rounded-xl bg-[linear-gradient(135deg,#06b6d4,#14b8a6)] px-3 py-2 text-sm font-medium text-white shadow-lg shadow-cyan-500/20 transition hover:-translate-y-px hover:shadow-cyan-500/30"
onClick={openDialog}
>
</button>
</div>
<div class="mt-4 flex min-h-0 flex-1 flex-col gap-4 xl:flex-row">
<div class="mt-3 flex min-h-0 flex-1 flex-col gap-3 xl:flex-row">
<AccountSidebar
accounts={accounts()}
selectedAccountId={selectedAccountId()}
@@ -682,6 +811,7 @@ const Account = () => {
accountStore.getState().setRecordType(value)
}
onChangeCourseRecordType={(value) => {
cancelPendingRecordRequest();
accountStore.getState().setCourseKind(value);
accountStore.getState().setSelectedCourseId(null);
accountStore.getState().setRecords([]);

View File

@@ -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>
);
};