订阅和规则组支持从文件导入,配置页与表单一批打磨
文件导入走两步:内容先经新端点 POST /api/provider-files 写进内核工作 目录的 providers/ 下(路径白名单校验,只认两个路径助手生成的形状), 成了再写 file 类型的声明——顺序反了内核重载会去读不存在的文件。 file 类型没有地址和自动更新(内核靠文件监视跟进),详情页照此展示, 编辑按钮禁用。ini 导入的取文件按钮换成同一个拖放模块。 订阅的更新间隔改成开关语义:内核只在 interval > 0 时才起定时拉取 (component/resource/fetcher.go:226-228),关掉开关就是不写这个键。 配置页加「更新所有订阅」,详情的「立刻更新」改叫「更新订阅」。 规则表从零开始写入时自动垫上内网直连——保留地址不直连的话第一条 代理规则一生效,访问局域网的流量就全进了代理;按钮改成「补全内网 直连」,只在有缺口时出现。 所有表单对话框点遮罩关闭;失败横幅右侧加关闭按钮。
This commit is contained in:
+15
-22
@@ -15,6 +15,7 @@ import { computed, ref, watch } from 'vue'
|
||||
import { parseIniRulesets, planIniImport } from '@shared/rules.ts'
|
||||
import type { IniImportPlan } from '@shared/rules.ts'
|
||||
import SelectBox from '../components/SelectBox.vue'
|
||||
import DropFile from '../components/DropFile.vue'
|
||||
import IconClose from '~icons/ri/close-line'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -37,7 +38,6 @@ const KIND_LABELS: Record<string, string> = {
|
||||
}
|
||||
|
||||
const dialog = ref<HTMLDialogElement | null>(null)
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const text = ref('')
|
||||
const mapping = ref<Record<string, string>>({})
|
||||
|
||||
@@ -85,21 +85,25 @@ watch(targets, (list) => {
|
||||
mapping.value = next
|
||||
})
|
||||
|
||||
async function pickFile(event: Event) {
|
||||
const file = (event.target as HTMLInputElement).files?.[0]
|
||||
if (file) text.value = await file.text()
|
||||
// 同一个文件选第二次也要触发 change
|
||||
if (fileInput.value) fileInput.value.value = ''
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (!canSubmit.value) return
|
||||
emit('submit', plan.value)
|
||||
}
|
||||
|
||||
/** 点在遮罩上就关闭。内容区的点击 target 是 form 里的元素,到不了 dialog 本身。 */
|
||||
function onBackdrop(event: MouseEvent) {
|
||||
if (event.target === dialog.value) emit('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<dialog ref="dialog" class="dlg" @close="emit('close')" @cancel.prevent="emit('close')">
|
||||
<dialog
|
||||
ref="dialog"
|
||||
class="dlg"
|
||||
@click="onBackdrop"
|
||||
@close="emit('close')"
|
||||
@cancel.prevent="emit('close')"
|
||||
>
|
||||
<form method="dialog" @submit.prevent="submit">
|
||||
<header class="dlg-head">
|
||||
<h2>导入 ini</h2>
|
||||
@@ -110,21 +114,14 @@ function submit() {
|
||||
<label class="row">
|
||||
<span class="k">内容</span>
|
||||
<span class="v">
|
||||
<DropFile accept=".ini,.conf,.txt" @load="(_, content) => (text = content)" />
|
||||
<textarea
|
||||
v-model="text"
|
||||
class="mono"
|
||||
rows="8"
|
||||
placeholder="粘贴 ini 文本,或从文件读取"
|
||||
placeholder="或把 ini 文本粘贴到这里"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept=".ini,.conf,.txt"
|
||||
hidden
|
||||
@change="pickFile"
|
||||
/>
|
||||
<button type="button" class="btn file" @click="fileInput?.click()">从文件读取</button>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
@@ -277,10 +274,6 @@ textarea {
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
|
||||
.file {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: var(--s-2) var(--s-4);
|
||||
border: 1px solid var(--c-border-strong);
|
||||
|
||||
Reference in New Issue
Block a user