feat/optimization-and-audio #1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
126
src/App.tsx
126
src/App.tsx
@@ -137,9 +137,6 @@ const renderInlineLinks = (text: string): JSX.Element[] => {
|
||||
const App: ParentComponent = (props) => {
|
||||
const location = useLocation();
|
||||
const [version] = createResource(versionApi);
|
||||
const [copyState, setCopyState] = createSignal<"idle" | "done" | "error">(
|
||||
"idle",
|
||||
);
|
||||
const [updateDialogOpen, setUpdateDialogOpen] = createSignal(false);
|
||||
const [updateCheckState, setUpdateCheckState] =
|
||||
createSignal<UpdateCheckState>("idle");
|
||||
@@ -197,31 +194,6 @@ const App: ParentComponent = (props) => {
|
||||
|
||||
return error instanceof Error ? error.message : "版本信息获取失败";
|
||||
});
|
||||
const versionPayloadText = createMemo(() =>
|
||||
[
|
||||
`Version: ${versionText()}`,
|
||||
`Mode: ${modeText()}`,
|
||||
`Commit: ${commitText()}`,
|
||||
`Build: ${buildText()}`,
|
||||
`Author: ${authorText()}`,
|
||||
`Email: ${emailText()}`,
|
||||
].join("\n"),
|
||||
);
|
||||
const updateSummaryText = createMemo(() => {
|
||||
if (updateCheckState() === "checking") {
|
||||
return "更新检查中...";
|
||||
}
|
||||
if (updateCheckState() === "available") {
|
||||
return `发现新版本:${latestRelease()?.tag_name ?? "-"}`;
|
||||
}
|
||||
if (updateCheckState() === "latest") {
|
||||
return "已是最新版本";
|
||||
}
|
||||
if (updateCheckState() === "error") {
|
||||
return updateCheckError() || "更新检查失败";
|
||||
}
|
||||
return "未检查更新";
|
||||
});
|
||||
const releaseNotesBlocks = createMemo(() =>
|
||||
parseMarkdownBlocks(latestRelease()?.body ?? ""),
|
||||
);
|
||||
@@ -231,6 +203,14 @@ const App: ParentComponent = (props) => {
|
||||
const releaseLink = createMemo(
|
||||
() => latestRelease()?.html_url || RELEASES_PAGE_URL,
|
||||
);
|
||||
const safeValue = (value: string) => (value === "unknown" ? "-" : value);
|
||||
const hasUpdateBadge = createMemo(() => updateCheckState() === "available");
|
||||
const updateDialogTitle = createMemo(() => {
|
||||
if (updateCheckState() === "available") {
|
||||
return `发现更新 ${latestRelease()?.tag_name ?? ""}`;
|
||||
}
|
||||
return "更新信息";
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
const unsubscribe = settingsStore.subscribe((state) => {
|
||||
@@ -242,16 +222,6 @@ const App: ParentComponent = (props) => {
|
||||
});
|
||||
});
|
||||
|
||||
const handleCopyVersion = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(versionPayloadText());
|
||||
setCopyState("done");
|
||||
} catch {
|
||||
setCopyState("error");
|
||||
}
|
||||
|
||||
window.setTimeout(() => setCopyState("idle"), 1800);
|
||||
};
|
||||
const performUpdateCheck = async (manual = false) => {
|
||||
if (updateCheckState() === "checking") {
|
||||
return;
|
||||
@@ -276,17 +246,17 @@ const App: ParentComponent = (props) => {
|
||||
setRuntimeTarget(target);
|
||||
|
||||
const hasNewVersion = isRemoteVersionNewer(versionText(), release.tag_name);
|
||||
setLatestRelease(release);
|
||||
setMatchedAsset(resolveAssetForRuntime(release.assets, target));
|
||||
|
||||
if (!hasNewVersion) {
|
||||
setUpdateCheckState("latest");
|
||||
if (manual) {
|
||||
setLatestRelease(release);
|
||||
setMatchedAsset(resolveAssetForRuntime(release.assets, target));
|
||||
setUpdateDialogOpen(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setLatestRelease(release);
|
||||
setMatchedAsset(resolveAssetForRuntime(release.assets, target));
|
||||
setUpdateCheckState("available");
|
||||
setUpdateDialogOpen(true);
|
||||
} catch (error) {
|
||||
@@ -425,48 +395,48 @@ const App: ParentComponent = (props) => {
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
{asideList().find((item) => isActive(item.url))?.label ?? "账号"}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">
|
||||
模式: {modeText()}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">
|
||||
调试: {isDebugMode() ? "已开启" : "已关闭"}
|
||||
</p>
|
||||
<p class="mt-3 text-xs font-medium tracking-[0.18em] text-cyan-700/75 uppercase">
|
||||
Runtime
|
||||
</p>
|
||||
<div class="mt-2 grid gap-1 text-xs text-zinc-500 xl:block">
|
||||
<p>Version: {versionText()}</p>
|
||||
<p>Commit: {commitText()}</p>
|
||||
<p>Build: {buildText()}</p>
|
||||
<p>Author: {authorText()}</p>
|
||||
<p>Email: {emailText()}</p>
|
||||
</div>
|
||||
<p
|
||||
class={`mt-2 text-xs ${updateCheckState() === "error" ? "text-rose-500" : "text-zinc-500"}`}
|
||||
>
|
||||
更新: {updateSummaryText()}
|
||||
</p>
|
||||
<div class="mt-3 flex flex-wrap items-center gap-2">
|
||||
<div class="mt-3 border-t border-zinc-200/80 pt-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-xs text-zinc-700 transition active:bg-zinc-200 hover:bg-zinc-100"
|
||||
onClick={() => void handleCopyVersion()}
|
||||
>
|
||||
{copyState() === "done"
|
||||
? "已复制"
|
||||
: copyState() === "error"
|
||||
? "复制失败"
|
||||
: "复制版本信息"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-cyan-200 bg-cyan-50 px-3 py-1.5 text-xs text-cyan-700 transition active:bg-cyan-200 hover:bg-cyan-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
class="flex w-full min-w-0 items-center gap-2 rounded-lg border border-zinc-200 bg-white/80 px-2.5 py-1.5 text-left text-xs text-zinc-500 transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={updateCheckState() === "checking"}
|
||||
onClick={() => void performUpdateCheck(true)}
|
||||
title="点击查看更新内容"
|
||||
>
|
||||
{updateCheckState() === "checking" ? "检查中..." : "检查更新"}
|
||||
<span class="text-zinc-400">版本</span>
|
||||
<span class="shrink-0 rounded-full border border-zinc-200 bg-zinc-100 px-2 py-0.5 text-[11px] font-medium text-zinc-700">
|
||||
{safeValue(versionText())}
|
||||
</span>
|
||||
{hasUpdateBadge() ? (
|
||||
<span
|
||||
class="h-2 w-2 shrink-0 rounded-full bg-rose-500"
|
||||
title={`新版本:${latestRelease()?.tag_name ?? "-"}`}
|
||||
/>
|
||||
) : null}
|
||||
<span class="ml-auto text-[11px] text-zinc-400">
|
||||
{updateCheckState() === "checking" ? "检查中..." : "查看更新"}
|
||||
</span>
|
||||
</button>
|
||||
<details class="mt-2 rounded-lg border border-zinc-200/80 bg-white/70 px-2.5 py-2">
|
||||
<summary class="cursor-pointer select-none text-[11px] text-zinc-500">
|
||||
系统诊断信息
|
||||
</summary>
|
||||
<div class="mt-2 space-y-1 text-xs">
|
||||
<p class={isDebugMode() ? "text-amber-600" : "text-zinc-600"}>
|
||||
Mode: {safeValue(modeText())}
|
||||
</p>
|
||||
<p class="truncate text-zinc-600">Commit: {safeValue(commitText())}</p>
|
||||
<p class="truncate text-zinc-600">Build: {safeValue(buildText())}</p>
|
||||
<p class="truncate text-zinc-600">Author: {safeValue(authorText())}</p>
|
||||
<p class="truncate text-zinc-600">Email: {safeValue(emailText())}</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{updateCheckState() === "error" ? (
|
||||
<p class="mt-2 text-xs text-rose-500">
|
||||
{updateCheckError() || "更新检查失败"}
|
||||
</p>
|
||||
) : null}
|
||||
{versionErrorText() ? (
|
||||
<p class="mt-2 text-xs text-rose-500">{versionErrorText()}</p>
|
||||
) : null}
|
||||
@@ -488,7 +458,7 @@ const App: ParentComponent = (props) => {
|
||||
}
|
||||
setUpdateDialogOpen(false);
|
||||
}}
|
||||
title={`发现更新 ${latestRelease()?.tag_name ?? ""}`}
|
||||
title={updateDialogTitle()}
|
||||
widthClass="max-w-3xl"
|
||||
closeOnOverlay={downloadState() !== "downloading"}
|
||||
footer={
|
||||
|
||||
@@ -283,7 +283,13 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
>
|
||||
<For each={props.recordTypeOptions}>
|
||||
{(item) => (
|
||||
<option value={item.value}>{item.label}</option>
|
||||
<option
|
||||
value={item.value}
|
||||
disabled={item.value === "/discuss"}
|
||||
class={item.value === "/discuss" ? "text-zinc-400" : ""}
|
||||
>
|
||||
{item.label}
|
||||
</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createMemo, createSignal, For, onCleanup, onMount } from "solid-js";
|
||||
import { createMemo, createSignal, For, onCleanup, onMount } from "solid-js";
|
||||
import { fetchDebugConfig, updateDebugConfig } from "~/service/debugLog";
|
||||
import { hostApi } from "~/service/wk";
|
||||
import { getMergedHosts, settingsStore } from "~/store/settings";
|
||||
@@ -117,367 +117,365 @@ const Setting = () => {
|
||||
void refreshDebugConfig();
|
||||
});
|
||||
|
||||
const panelClass =
|
||||
"rounded-[28px] border border-white/80 bg-white/85 p-5 shadow-[0_18px_50px_-28px_rgba(15,23,42,0.25)]";
|
||||
const sectionCardClass = "rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4";
|
||||
|
||||
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(125deg,rgba(255,255,255,0.96),rgba(240,249,255,0.95)_55%,rgba(236,254,255,0.9))] px-6 py-5 shadow-[0_22px_50px_-32px_rgba(15,23,42,0.22)]">
|
||||
<div>
|
||||
<p class="text-xs font-medium tracking-[0.26em] text-cyan-700/75 uppercase">
|
||||
Settings Center
|
||||
<p class="text-xs font-semibold tracking-[0.28em] text-cyan-700/80 uppercase">
|
||||
Preference Center
|
||||
</p>
|
||||
<h1 class="mt-2 text-2xl font-semibold text-zinc-900">偏好设置</h1>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
管理本地缓存、界面偏好和 Host 来源策略
|
||||
统一管理本地缓存、界面体验和 Host 来源策略
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-white/90 px-4 py-3 text-right shadow-sm">
|
||||
<p class="text-sm font-medium text-zinc-800">本地优先</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">
|
||||
<div class="rounded-2xl border border-zinc-200/80 bg-white/90 px-4 py-3 text-right shadow-sm">
|
||||
<p class="text-sm font-medium text-zinc-800">策略:本地优先</p>
|
||||
<p class="mt-1 text-xs leading-5 text-zinc-500">
|
||||
远端 Host 获取后会与本地列表合并去重
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 min-h-0 flex-1 overflow-y-auto pr-1">
|
||||
<div class="grid gap-4 xl:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="rounded-[28px] border border-white/80 bg-white/85 p-4 shadow-[0_18px_50px_-28px_rgba(15,23,42,0.25)]">
|
||||
<div class="mt-5 min-h-0 flex-1 overflow-hidden">
|
||||
<div class="grid h-full min-h-full w-full gap-5 lg:grid-cols-2">
|
||||
<div class="flex min-h-0 h-full flex-col gap-5 overflow-y-auto pr-1">
|
||||
<section class={panelClass}>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p class="text-base font-semibold text-zinc-900">本地数据</p>
|
||||
<p class="mt-0.5 text-xs text-zinc-500">
|
||||
管理本地缓存数据
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-zinc-900">本地数据</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">管理本地缓存数据</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-rose-200 bg-rose-50 px-2.5 py-1.5 text-xs text-rose-600 transition hover:bg-rose-100 active:bg-rose-200"
|
||||
class="rounded-lg border border-rose-200 bg-rose-50 px-3 py-1.5 text-xs text-rose-600 transition hover:bg-rose-100 active:bg-rose-200"
|
||||
onClick={() => settingsStore.getState().clearAllPersistedData()}
|
||||
>
|
||||
清空全部
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 grid gap-2">
|
||||
<div class="flex items-center justify-between gap-2 rounded-xl border border-zinc-200 bg-zinc-50/80 px-3 py-2">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-zinc-900">账号缓存</p>
|
||||
<p class="text-xs text-zinc-500">账号、课程和登录信息</p>
|
||||
<div class="mt-4 grid gap-3">
|
||||
<div class={sectionCardClass}>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-zinc-900">账号缓存</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">账号、课程和登录信息</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2.5 py-1.5 text-xs text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200"
|
||||
onClick={() =>
|
||||
settingsStore.getState().clearPersistedSection("accounts")
|
||||
}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2 py-1 text-xs text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200"
|
||||
onClick={() =>
|
||||
settingsStore.getState().clearPersistedSection("accounts")
|
||||
}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-2 rounded-xl border border-zinc-200 bg-zinc-50/80 px-3 py-2">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-zinc-900">记录缓存</p>
|
||||
<p class="text-xs text-zinc-500">课程记录和筛选类型</p>
|
||||
<div class={sectionCardClass}>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-zinc-900">记录缓存</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">课程记录和筛选类型</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2.5 py-1.5 text-xs text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200"
|
||||
onClick={() =>
|
||||
settingsStore.getState().clearPersistedSection("records")
|
||||
}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2 py-1 text-xs text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200"
|
||||
onClick={() =>
|
||||
settingsStore.getState().clearPersistedSection("records")
|
||||
}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-2 rounded-xl border border-zinc-200 bg-zinc-50/80 px-3 py-2">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-zinc-900">日志缓存</p>
|
||||
<p class="text-xs text-zinc-500">任务日志历史</p>
|
||||
<div class={sectionCardClass}>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-zinc-900">日志缓存</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">任务日志历史</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2.5 py-1.5 text-xs text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200"
|
||||
onClick={() =>
|
||||
settingsStore.getState().clearPersistedSection("logs")
|
||||
}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2 py-1 text-xs text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200"
|
||||
onClick={() =>
|
||||
settingsStore.getState().clearPersistedSection("logs")
|
||||
}
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rounded-[28px] border border-white/80 bg-white/85 p-5 shadow-[0_18px_50px_-28px_rgba(15,23,42,0.25)]">
|
||||
<p class="text-lg font-semibold text-zinc-900">界面偏好</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
根据你的使用习惯调整侧栏、日志和展示密度
|
||||
</p>
|
||||
</div>
|
||||
<section class={panelClass}>
|
||||
<div class="border-b border-zinc-200/70 pb-4">
|
||||
<p class="text-lg font-semibold text-zinc-900">界面偏好</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
根据使用习惯调整日志行为、展示密度和侧栏尺寸
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-4">
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">手动开启调试</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
开启后显示后端日志页,并应用本地代理 / SSL 调试配置
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<div class={`${sectionCardClass} md:col-span-2`}>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">手动开启调试</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
开启后显示后端日志页,并应用本地代理 / SSL 调试配置
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state().debugEnabled}
|
||||
disabled={debugSyncing()}
|
||||
onChange={(event) =>
|
||||
void handleDebugToggle(event.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-3 rounded-xl border border-zinc-200 bg-white px-4 py-3 text-sm text-zinc-600">
|
||||
<p>后端状态:{backendDebugState()?.enabled ? "已开启" : "已关闭"}</p>
|
||||
<p class="mt-1">编译模式:{backendDebugState()?.buildMode ?? "-"}</p>
|
||||
<p class="mt-1">
|
||||
本地代理:
|
||||
{backendDebugState()?.proxyConfigured
|
||||
? backendDebugState()?.proxy
|
||||
: "未配置"}
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
跳过 SSL 校验:
|
||||
{backendDebugState()?.skipSSLVerify ? "已配置" : "未配置"}
|
||||
</p>
|
||||
</div>
|
||||
{debugError() ? (
|
||||
<div class="mt-3 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-600">
|
||||
{debugError()}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div class={sectionCardClass}>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">日志自动滚动</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">新日志出现时自动滚动到底部</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state().autoScrollLogs}
|
||||
onChange={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setAutoScrollLogs(event.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class={sectionCardClass}>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">显示日志时间戳</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">日志输出追加格式化时间</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state().showLogTimestamps}
|
||||
onChange={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setShowLogTimestamps(event.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class={sectionCardClass}>
|
||||
<label class="block">
|
||||
<p class="font-medium text-zinc-900">界面密度</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">选择更舒适或更紧凑的展示方式</p>
|
||||
<select
|
||||
class="mt-3 w-full rounded-xl border border-zinc-200 bg-white px-4 py-3 transition outline-none focus:border-cyan-400"
|
||||
value={state().densityMode}
|
||||
onChange={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setDensityMode(
|
||||
event.currentTarget.value as
|
||||
| "comfortable"
|
||||
| "compact",
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="comfortable">舒适</option>
|
||||
<option value="compact">紧凑</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class={sectionCardClass}>
|
||||
<label class="block">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">日志字号</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">当前:{state().logFontSize}px</p>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mt-4 w-full"
|
||||
type="range"
|
||||
min="11"
|
||||
max="16"
|
||||
value={state().logFontSize}
|
||||
onInput={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setLogFontSize(Number(event.currentTarget.value))
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class={`${sectionCardClass} md:col-span-2`}>
|
||||
<label class="block">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">侧栏宽度</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">当前:{state().sidebarWidth}px</p>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mt-4 w-full"
|
||||
type="range"
|
||||
min="280"
|
||||
max="380"
|
||||
step="10"
|
||||
value={state().sidebarWidth}
|
||||
onInput={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setSidebarWidth(Number(event.currentTarget.value))
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-0 h-full flex-col gap-5 overflow-y-auto pr-1">
|
||||
<section class={panelClass}>
|
||||
<div class="border-b border-zinc-200 pb-4">
|
||||
<p class="text-lg font-semibold text-zinc-900">Host 配置策略</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
远端 Host 后续通过接口请求,本地 Host 手动添加,最终合并并去重,优先保留本地配置。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class={`${sectionCardClass} mt-4`}>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<p class="font-medium text-zinc-900">添加本地 Host</p>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={isLoadingRemoteHosts()}
|
||||
onClick={() => void loadRemoteHosts()}
|
||||
>
|
||||
{isLoadingRemoteHosts() ? "获取中..." : "获取远程 Host"}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 grid gap-3 md:grid-cols-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state().debugEnabled}
|
||||
disabled={debugSyncing()}
|
||||
onChange={(event) =>
|
||||
void handleDebugToggle(event.currentTarget.checked)
|
||||
}
|
||||
class="rounded-xl border border-zinc-200 bg-white px-4 py-3 transition outline-none focus:border-cyan-400"
|
||||
value={hostLabel()}
|
||||
onInput={(event) => setHostLabel(event.currentTarget.value)}
|
||||
placeholder="名称,如:校内测试"
|
||||
/>
|
||||
<input
|
||||
class="rounded-xl border border-zinc-200 bg-white px-4 py-3 transition outline-none focus:border-cyan-400"
|
||||
value={hostValue()}
|
||||
onInput={(event) => setHostValue(event.currentTarget.value)}
|
||||
placeholder="Host,如:example.com"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-3 rounded-xl border border-zinc-200 bg-white px-4 py-3 text-sm text-zinc-600">
|
||||
<p>后端状态:{backendDebugState()?.enabled ? "已开启" : "已关闭"}</p>
|
||||
<p class="mt-1">
|
||||
编译模式:{backendDebugState()?.buildMode ?? "-"}
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
本地代理:
|
||||
{backendDebugState()?.proxyConfigured
|
||||
? backendDebugState()?.proxy
|
||||
: "未配置"}
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
跳过 SSL 校验:
|
||||
{backendDebugState()?.skipSSLVerify ? "已配置" : "未配置"}
|
||||
</p>
|
||||
</div>
|
||||
{debugError() ? (
|
||||
<button
|
||||
type="button"
|
||||
class="mt-3 rounded-xl bg-cyan-500 px-4 py-2 text-sm text-white transition hover:bg-cyan-600 active:bg-cyan-700"
|
||||
onClick={addLocalHost}
|
||||
>
|
||||
添加本地 Host
|
||||
</button>
|
||||
|
||||
{hostError() ? (
|
||||
<div class="mt-3 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-600">
|
||||
{debugError()}
|
||||
{hostError()}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">日志自动滚动</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
新日志出现时自动滚动到最底部
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state().autoScrollLogs}
|
||||
onChange={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setAutoScrollLogs(event.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<section class={panelClass}>
|
||||
<div class="flex items-center justify-between border-b border-zinc-200 pb-4">
|
||||
<p class="text-lg font-semibold text-zinc-900">Host 列表</p>
|
||||
<p class="text-xs text-zinc-500">本地配置优先覆盖远端</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">显示日志时间戳</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
后续日志输出可以追加格式化时间
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={state().showLogTimestamps}
|
||||
onChange={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setShowLogTimestamps(event.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<label class="block">
|
||||
<p class="font-medium text-zinc-900">界面密度</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
选择更舒适或更紧凑的展示方式
|
||||
</p>
|
||||
<select
|
||||
class="mt-3 w-full rounded-xl border border-zinc-200 bg-white px-4 py-3 transition outline-none focus:border-cyan-400"
|
||||
value={state().densityMode}
|
||||
onChange={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setDensityMode(
|
||||
event.currentTarget.value as
|
||||
| "comfortable"
|
||||
| "compact",
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="comfortable">舒适</option>
|
||||
<option value="compact">紧凑</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<label class="block">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">日志字号</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
当前:{state().logFontSize}px
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mt-4 w-full"
|
||||
type="range"
|
||||
min="11"
|
||||
max="16"
|
||||
value={state().logFontSize}
|
||||
onInput={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setLogFontSize(Number(event.currentTarget.value))
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<label class="block">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">侧栏宽度</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
当前:{state().sidebarWidth}px
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="mt-4 w-full"
|
||||
type="range"
|
||||
min="280"
|
||||
max="380"
|
||||
step="10"
|
||||
value={state().sidebarWidth}
|
||||
onInput={(event) =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.setSidebarWidth(Number(event.currentTarget.value))
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="rounded-[28px] border border-white/80 bg-white/85 p-5 shadow-[0_18px_50px_-28px_rgba(15,23,42,0.25)]">
|
||||
<div class="border-b border-zinc-200 pb-4">
|
||||
<p class="text-lg font-semibold text-zinc-900">Host 配置策略</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
远端 Host 后续通过接口请求,本地 Host
|
||||
手动添加,最终合并并去重,优先保留本地配置。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<p class="font-medium text-zinc-900">添加本地 Host</p>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-700 transition hover:bg-zinc-100 active:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={isLoadingRemoteHosts()}
|
||||
onClick={() => void loadRemoteHosts()}
|
||||
>
|
||||
{isLoadingRemoteHosts() ? "获取中..." : "获取远程 Host"}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 grid gap-3 md:grid-cols-2">
|
||||
<input
|
||||
class="rounded-xl border border-zinc-200 bg-white px-4 py-3 transition outline-none focus:border-cyan-400"
|
||||
value={hostLabel()}
|
||||
onInput={(event) => setHostLabel(event.currentTarget.value)}
|
||||
placeholder="名称,如:校内测试"
|
||||
/>
|
||||
<input
|
||||
class="rounded-xl border border-zinc-200 bg-white px-4 py-3 transition outline-none focus:border-cyan-400"
|
||||
value={hostValue()}
|
||||
onInput={(event) => setHostValue(event.currentTarget.value)}
|
||||
placeholder="Host,如:example.com"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-3 rounded-xl bg-cyan-500 px-4 py-2 text-sm text-white transition hover:bg-cyan-600 active:bg-cyan-700"
|
||||
onClick={addLocalHost}
|
||||
>
|
||||
添加本地 Host
|
||||
</button>
|
||||
|
||||
{hostError() ? (
|
||||
<div class="mt-3 rounded-xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-600">
|
||||
{hostError()}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-4 xl:grid-cols-2">
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<p class="font-medium text-zinc-900">本地 Host</p>
|
||||
<div class="mt-3 flex flex-col gap-3">
|
||||
<For each={state().localHosts}>
|
||||
{(item) => (
|
||||
<div class="rounded-xl border border-zinc-200 bg-white px-4 py-3">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">
|
||||
{item.label}
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
{item.host}
|
||||
</p>
|
||||
<div class="mt-4 grid gap-4 xl:grid-cols-2">
|
||||
<div class={sectionCardClass}>
|
||||
<p class="font-medium text-zinc-900">本地 Host</p>
|
||||
<div class="mt-3 flex flex-col gap-3">
|
||||
<For each={state().localHosts}>
|
||||
{(item) => (
|
||||
<div class="rounded-xl border border-zinc-200 bg-white px-4 py-3">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="font-medium text-zinc-900">{item.label}</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">{item.host}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-rose-200 px-3 py-1 text-xs text-rose-600 transition hover:bg-rose-50 active:bg-rose-100"
|
||||
onClick={() =>
|
||||
settingsStore.getState().removeLocalHost(item.host)
|
||||
}
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-rose-200 px-3 py-1 text-xs text-rose-600 transition hover:bg-rose-50 active:bg-rose-100"
|
||||
onClick={() =>
|
||||
settingsStore
|
||||
.getState()
|
||||
.removeLocalHost(item.host)
|
||||
}
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-zinc-200 bg-zinc-50/80 p-4">
|
||||
<p class="font-medium text-zinc-900">合并结果</p>
|
||||
<div class="mt-3 flex flex-col gap-3">
|
||||
<For each={mergedHosts()}>
|
||||
{(item) => (
|
||||
<div class="rounded-xl border border-zinc-200 bg-white px-4 py-3">
|
||||
<p class="font-medium text-zinc-900">{item.label}</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">{item.host}</p>
|
||||
<p class="mt-2 text-xs text-cyan-700">
|
||||
来源:{item.source === "local" ? "本地优先" : "远端"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
<div class={sectionCardClass}>
|
||||
<p class="font-medium text-zinc-900">合并结果</p>
|
||||
<div class="mt-3 flex flex-col gap-3">
|
||||
<For each={mergedHosts()}>
|
||||
{(item) => (
|
||||
<div class="rounded-xl border border-zinc-200 bg-white px-4 py-3">
|
||||
<p class="font-medium text-zinc-900">{item.label}</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">{item.host}</p>
|
||||
<p class="mt-2 text-xs text-cyan-700">
|
||||
来源:{item.source === "local" ? "本地优先" : "远端"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user