设置页重做:配置文件下载上传、内核参数、资源、免责声明

配置每次落盘都打顶层 patchbay: 标记(盐加版本的哈希)。内核忽略未知
顶层键(yaml.Unmarshal 没开严格模式,config/config.go:612)。刻意不对
内容做哈希:本项目只接管部分段落,手改其余段落是正常用法,内容哈希
会把合法手改判成无效。上传配置时靠标记分流:有标记的确认后替换,
没有的用更重的措辞确认导入;两者都走 config-store 完整保护链路
(新端点 POST /api/config/import)。自动备份列表从界面撤下,改成
下载当前配置 + 拖放上传。

内核参数一组编辑五个老式顶层端口和 keep-alive-interval(config.go:444,
整数秒),走带基准指纹的保存链路。资源一组接 POST /configs/geo
(同步接口,透传给了 300 秒超时)。测速地址成为本地偏好,延迟测试
读它;prefs 模块顶层的浏览器全局加了存在性守卫——api.ts 开始引用
prefs,而 bun test 环境没有 localStorage。

退出登录挪到侧栏底部;密度设置从设置页撤下(侧栏按钮还在);
免责声明和关于做成子页(左上角返回),登录页输入框下加同一份
免责声明的弹层入口。
This commit is contained in:
2026-08-12 01:01:33 +08:00
parent 013f537eaa
commit 8eb98e2e9a
12 changed files with 792 additions and 147 deletions
+106 -1
View File
@@ -1,8 +1,23 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, watch } from 'vue'
import { DISCLAIMER, DISCLAIMER_TITLE } from '../legal'
import IconClose from '~icons/ri/close-line'
const emit = defineEmits<{ submit: [token: string] }>()
const value = ref('')
const legalOpen = ref(false)
const legalDialog = ref<HTMLDialogElement | null>(null)
watch(legalOpen, (open) => {
if (open) legalDialog.value?.showModal()
else legalDialog.value?.close()
})
/** 点在遮罩上就关闭。内容区的点击 target 是里面的元素,到不了 dialog 本身。 */
function onBackdrop(event: MouseEvent) {
if (event.target === legalDialog.value) legalOpen.value = false
}
</script>
<template>
@@ -23,7 +38,26 @@ const value = ref('')
<p class="where">
令牌位于服务端配置文件的同级目录 <code>.patchbay-token</code>,启动时也会输出到控制台。
</p>
<button type="button" class="legal" @click="legalOpen = true">
使用 Patchbay 即视为同意《免责声明》
</button>
</form>
<dialog
ref="legalDialog"
class="dlg"
@click="onBackdrop"
@close="legalOpen = false"
@cancel.prevent="legalOpen = false"
>
<div class="dlg-body">
<header class="dlg-head">
<h2>{{ DISCLAIMER_TITLE }}</h2>
<button type="button" class="x" @click="legalOpen = false"><IconClose /></button>
</header>
<p v-for="(para, i) in DISCLAIMER" :key="i" class="para">{{ para }}</p>
</div>
</dialog>
</div>
</template>
@@ -85,4 +119,75 @@ h1 {
line-height: 1.6;
margin-top: var(--s-2);
}
.legal {
align-self: flex-start;
padding: 0;
border: none;
background: none;
color: var(--c-text-faint);
font-size: var(--t-xs);
cursor: pointer;
text-decoration: underline;
text-underline-offset: 3px;
}
.legal:hover {
color: var(--c-text-soft);
}
.dlg {
width: min(480px, calc(100vw - 2 * var(--s-5)));
padding: 0;
border: 1px solid var(--c-border-strong);
border-radius: var(--r-lg);
background: var(--c-surface);
color: var(--c-text);
}
.dlg::backdrop {
background: rgb(0 0 0 / 0.45);
}
.dlg-body {
padding: var(--s-5);
}
.dlg-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--s-3);
}
.dlg-head h2 {
font-size: var(--t-lg);
font-weight: 600;
}
.x {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 1px solid transparent;
border-radius: var(--r-sm);
background: none;
color: var(--c-text-faint);
cursor: pointer;
}
.x:hover {
background: var(--c-surface-sunken);
color: var(--c-text);
}
.para {
color: var(--c-text-soft);
font-size: var(--t-sm);
line-height: var(--line);
}
.para + .para {
margin-top: var(--s-3);
}
</style>