消化一批 P3:GEOIP 附加参数、坏 JSON 跳行、兜底丢弃;请求连接两页保活

ini 里 []GEOIP,CN,no-resolve 的附加参数原来被并进类别名,生成字段错位
的规则;现在拆出来放在目标之后。表里已有 MATCH 时配方的 []FINAL 不再
追加成死行,改为丢弃并在成功提示里注明——已有兜底是用户自己安排的
分流终点。

遥测流里偶发的坏 JSON 行改为跳过,原来一行坏数据会杀掉整个流进 5 秒
重试循环。请求页:跟随底部的阈值从 24px 降到 8px(原值比行高还大,
向上滚不足一行就被新日志拽回);silent 级别下的空状态不再说「没有流量
经过」。logs.test 的 localStorage 桩用完删掉,bun test 单进程跑,全局
残留会影响之后的测试文件。api.ts 的 format 注释照 provider.go:37 纠正。

请求页和连接页包进 KeepAlive:切走不销毁,筛选词、排序、滚动位置留住。
KeepAlive 常驻、子组件自己 v-if 进出;连接页的轮询改挂 activated——
挂载时它也触发,继续用 onMounted 会叠两个定时器,切走时停掉轮询。
This commit is contained in:
2026-08-12 03:07:56 +08:00
parent 275f536429
commit 158dc9fca7
9 changed files with 85 additions and 26 deletions
+9
View File
@@ -202,6 +202,15 @@ custom_proxy_group=🚀 节点选择\`select\`[]AUTO
it('custom_proxy_group 和残缺行不产生条目', () => {
expect(parseIniRulesets('ruleset=\nruleset=只有目标,\ncustom_proxy_group=x`select`y')).toEqual([])
})
it('GEOIP 类别后的附加参数不并进类别名,生成规则时放在目标之后', () => {
const entries = parseIniRulesets('ruleset=🎯 全球直连,[]GEOIP,CN,no-resolve')
expect(entries).toEqual([
{ target: '🎯 全球直连', kind: 'geoip', source: 'CN', flags: ['no-resolve'] },
])
const plan = planIniImport(entries, { '🎯 全球直连': 'DIRECT' }, new Set())
expect(plan.rules).toEqual(['GEOIP,CN,DIRECT,no-resolve'])
})
})
describe('ini 导入计划', () => {
+15 -7
View File
@@ -253,6 +253,8 @@ export interface IniRuleset {
kind: 'url' | 'file' | 'geoip' | 'geosite' | 'final'
/** kind 为 url/file 时是来源,为 geoip/geosite 时是类别名,final 时为空 */
source: string
/** geoip/geosite 类别后面的附加参数(no-resolve 这类)。生成规则时放在目标之后 */
flags?: string[]
}
/**
@@ -281,10 +283,19 @@ export function parseIniRulesets(text: string): IniRuleset[] {
const inline = source.slice(2).trim()
const geo = /^(GEOIP|GEOSITE)\s*,\s*(.+)$/i.exec(inline)
if (geo) {
// []GEOIP,CN,no-resolve:第二段才是类别,再往后是 no-resolve 这类附加参数。
// 附加参数在 mihomo 规则里的位置是目标之后,混进类别名会生成字段错位的规则
const fields = geo[2]!
.split(',')
.map((field) => field.trim())
.filter(Boolean)
const [category, ...flags] = fields
if (!category) continue
result.push({
target,
kind: geo[1]!.toUpperCase() === 'GEOIP' ? 'geoip' : 'geosite',
source: geo[2]!.trim(),
source: category,
...(flags.length > 0 ? { flags } : {}),
})
} else if (inline.toUpperCase() === 'FINAL' || inline.toUpperCase() === 'MATCH') {
result.push({ target, kind: 'final', source: '' })
@@ -342,12 +353,9 @@ export function planIniImport(
const target = mapping[entry.target]
if (!target) continue
if (entry.kind === 'geoip') {
rules.push(`GEOIP,${entry.source},${target}`)
continue
}
if (entry.kind === 'geosite') {
rules.push(`GEOSITE,${entry.source},${target}`)
if (entry.kind === 'geoip' || entry.kind === 'geosite') {
const type = entry.kind === 'geoip' ? 'GEOIP' : 'GEOSITE'
rules.push([type, entry.source, target, ...(entry.flags ?? [])].join(','))
continue
}
if (entry.kind === 'final') {