追加 Hosts、路径撞名拦截、连接页表头随行滚动、GEO 借内核通路
设置页内核参数加 hosts 行,铅笔进弹窗编辑(顶层 hosts: 键, config.go:454,值单个或数组 parseHosts config.go:1131)。弹窗里随便改、 保存才一次性提交——写配置有几秒的校验重载,逐行失焦提交会把等待塞进 每次输入。没动过的行原样写回,数组不被重整形。 订阅和规则组新增时查缓存路径撞名:路径由名字净化而来,「订阅 A」和 「订阅/A」是两个名字、同一个文件,名字查重挡不住,会互相覆盖缓存。 纯函数在 shared 带测试,页面提交前拦。 连接页表头挪进滚动容器加 sticky:.scroller 声明 overflow-y 后按规范 连带拿到横向滚动上下文,表头留在外面时窄屏横向滚动两者脱节。表头 40px 的窗口偏移由 overscan 8 行吸收,不配 scrollMargin。 GEO 单文件下载先借内核的混合端口出去再退直连——内核自己更新 GEO 走 的是它的分流,Patchbay 直连 GitHub 不一定通。ECharts 拆独立 chunk, 业务代码改动不再让这 500KB 跟着换 hash。
This commit is contained in:
@@ -24,6 +24,7 @@ import { DISCLAIMER, DISCLAIMER_TITLE } from '../legal'
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||
import InlineEdit from '../components/InlineEdit.vue'
|
||||
import SelectBox from '../components/SelectBox.vue'
|
||||
import HostsForm from './HostsForm.vue'
|
||||
import IconRight from '~icons/ri/arrow-right-s-line'
|
||||
import IconBack from '~icons/ri/arrow-left-s-line'
|
||||
import IconDownload from '~icons/ri/download-2-line'
|
||||
@@ -60,17 +61,19 @@ function saveTestUrl(value: string) {
|
||||
|
||||
const saving = ref(false)
|
||||
|
||||
/** 所有改配置文件的动作都从这里走。 */
|
||||
async function commitConfig(next: ConfigObject, success: string) {
|
||||
if (saving.value) return
|
||||
/** 所有改配置文件的动作都从这里走。反馈走轻提示,返回值告知成败。 */
|
||||
async function commitConfig(next: ConfigObject, success: string): Promise<boolean> {
|
||||
if (saving.value) return false
|
||||
saving.value = true
|
||||
try {
|
||||
const result = await api.applyConfig(next, props.revision)
|
||||
// 内核不在线时只写了文件没重载,如实说,不能只报成功
|
||||
toast(result.reloaded ? success : `${success}(${result.reloadMessage})`)
|
||||
emit('saved')
|
||||
return true
|
||||
} catch (error) {
|
||||
toastError(error)
|
||||
return false
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
@@ -101,6 +104,25 @@ function saveNumberKey(key: string, value: string, success: string) {
|
||||
void commitConfig(next, success)
|
||||
}
|
||||
|
||||
/* ── 追加 Hosts ───────────────────────────────── */
|
||||
|
||||
const hostsOpen = ref(false)
|
||||
|
||||
const hostsOf = (): ConfigObject => {
|
||||
const value = props.data.hosts
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
? (value as ConfigObject)
|
||||
: {}
|
||||
}
|
||||
|
||||
async function submitHosts(hosts: ConfigObject) {
|
||||
const next = clone()
|
||||
if (Object.keys(hosts).length === 0) delete next.hosts
|
||||
else next.hosts = hosts
|
||||
const ok = await commitConfig(next, 'hosts 已保存')
|
||||
if (ok) hostsOpen.value = false
|
||||
}
|
||||
|
||||
/* ── 资源(GEO 数据库) ───────────────────────── */
|
||||
|
||||
const geoFiles = ref<GeoFileStatus[] | null>(null)
|
||||
@@ -451,6 +473,25 @@ const backend = location.origin
|
||||
@submit="(v) => saveNumberKey('keep-alive-interval', v, 'TCP 保持活动间隔已保存')"
|
||||
/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="key mono">hosts</span>
|
||||
<span class="g-right">
|
||||
<span :class="['g-meta', Object.keys(hostsOf()).length === 0 && 'faint']">
|
||||
{{
|
||||
Object.keys(hostsOf()).length > 0 ? `${Object.keys(hostsOf()).length} 条` : '未设置'
|
||||
}}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="ico"
|
||||
title="编辑"
|
||||
:disabled="saving"
|
||||
@click="hostsOpen = true"
|
||||
>
|
||||
<IconEdit />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="group">
|
||||
@@ -601,6 +642,13 @@ const backend = location.origin
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<HostsForm
|
||||
:open="hostsOpen"
|
||||
:hosts="hostsOf()"
|
||||
@close="hostsOpen = false"
|
||||
@save="submitHosts"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
:open="pendingUpload !== null"
|
||||
:message="uploadMessage()"
|
||||
|
||||
Reference in New Issue
Block a user