路由页一轮交互细化:草稿预览、预选、若干修正
上半部分收敛成一条规则:草稿和已保存一致时显示内核现状;不一致时显示 按草稿保存后的样子,将移除的画删除线、新增的虚线描边,即将被移除的当前 选中用危险色警示。点击语义只看卡片:现在就存在的成员点了立刻生效,保存 后才加入的点了是预选,保存成功后再作为运行时动作选上。预览的集合运算在 web/member-preview.ts,组件里只留接线。 其余:订阅选择器重写成可搜索多选(原平铺标签订阅一多就是一面墙);删除 入站换成页面内确认对话框;列表右列解析到最终出口并标注链式;「高级配置」 更名「配置」;表单重置改按内容比较(按引用的话任何一次运行时刷新都会冲掉 未保存的草稿,watch 传返回数组的 getter 同样触发这个问题);保存后先刷 运行时再换配置数据,避免卡片区闪空。
This commit is contained in:
@@ -14,7 +14,9 @@ import {
|
||||
chainGroupName,
|
||||
groupUses,
|
||||
panelGroups,
|
||||
proxyGroupName,
|
||||
pruneGenerated,
|
||||
sharedPeers,
|
||||
syncGenerated,
|
||||
readSetup,
|
||||
userProviders,
|
||||
@@ -27,6 +29,7 @@ import type { GroupView } from '../runtime'
|
||||
import { optimisticSelect, providerLines, reloadRuntime, runtime } from '../runtime'
|
||||
import { useDelayTest } from '../delay-test'
|
||||
import { density } from '../prefs'
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||
import InboundList from '../components/InboundList.vue'
|
||||
import InboundDetail from './InboundDetail.vue'
|
||||
import InboundForm from './InboundForm.vue'
|
||||
@@ -91,7 +94,12 @@ const panel = computed(() =>
|
||||
: { groups: [], shared: false },
|
||||
)
|
||||
|
||||
/** 共用的组是别人的,改不了它的来源,所以高级配置也不给。 */
|
||||
/** 选中这一路落在共用组上时,还有哪些入站也走同一个组。决定「共用」标记显不显示。 */
|
||||
const sharedWith = computed(() =>
|
||||
selected.value ? sharedPeers(props.data, runtime.value, inbounds.value, selected.value) : [],
|
||||
)
|
||||
|
||||
/** 共用的组是别人的,改不了它的来源,所以配置表单也不给。 */
|
||||
const setup = computed<OutboundSetup | null>(() =>
|
||||
selected.value && !panel.value.shared ? readSetup(props.data, selected.value.name) : null,
|
||||
)
|
||||
@@ -115,8 +123,9 @@ async function commit(raw: ConfigObject, success: string) {
|
||||
done.value = ''
|
||||
notice.value = ''
|
||||
try {
|
||||
await api.applyConfig(next, props.revision)
|
||||
done.value = success
|
||||
const result = await api.applyConfig(next, props.revision)
|
||||
// 没重载有两种情况(内容没有变化、内核不在线只写了文件),都要如实说,不能只报成功
|
||||
done.value = result.reloaded ? success : `${success}(${result.reloadMessage})`
|
||||
emit('saved')
|
||||
} catch (error) {
|
||||
failure.value =
|
||||
@@ -128,11 +137,25 @@ async function commit(raw: ConfigObject, success: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存高级配置:改的是这一路的组从哪些订阅取成员、筛掉哪些。 */
|
||||
async function saveSetup(next: OutboundSetup) {
|
||||
/** 保存这一路的配置:组从哪些订阅取成员、筛掉哪些、开不开链式代理。 */
|
||||
async function saveSetup(next: OutboundSetup, picks: { proxy: string | null; chain: string | null }) {
|
||||
const target = selected.value
|
||||
if (!target || saving.value) return
|
||||
await commit(applySetup(clone(), target.name, next), `${target.name} 的代理配置已保存`)
|
||||
if (failure.value) return
|
||||
// 草稿态里预选的线路在保存成功、组按新配置就位之后逐个选上。这是运行时动作,
|
||||
// 失败不影响已保存的配置:组停在内核的默认选中上,刷新后显示的就是真实状态。
|
||||
const picked: Array<[string, string | null]> = [
|
||||
[proxyGroupName(target.name), picks.proxy],
|
||||
[chainGroupName(target.name), picks.chain],
|
||||
]
|
||||
let selectedAny = false
|
||||
for (const [group, member] of picked) {
|
||||
if (!member) continue
|
||||
await api.selectProxy(group, member).catch(() => {})
|
||||
selectedAny = true
|
||||
}
|
||||
if (selectedAny) await reloadRuntime()
|
||||
}
|
||||
|
||||
/** 把一路还没接管的入站改成专属代理组模式。 */
|
||||
@@ -192,10 +215,19 @@ async function selectMember(group: string, member: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeSelected() {
|
||||
/** 待确认删除的那一路。非 null 时确认对话框打开着。 */
|
||||
const removing = ref<Inbound | null>(null)
|
||||
|
||||
function askRemove() {
|
||||
const target = selected.value
|
||||
if (!target || target.listenerIndex === null || saving.value) return
|
||||
if (!confirm(`删除入站「${target.name}」?端口 ${target.port} 将停止监听。`)) return
|
||||
removing.value = target
|
||||
}
|
||||
|
||||
async function removeConfirmed() {
|
||||
const target = removing.value
|
||||
removing.value = null
|
||||
if (!target || target.listenerIndex === null || saving.value) return
|
||||
|
||||
const next = clone()
|
||||
const listeners = (next.listeners as ConfigObject[]).filter((_, i) => i !== target.listenerIndex)
|
||||
@@ -246,13 +278,14 @@ async function removeSelected() {
|
||||
v-if="selected"
|
||||
:inbound="selected"
|
||||
:panel="panel"
|
||||
:shared-with="sharedWith"
|
||||
:chain="chainView"
|
||||
:subscriptions="subscriptions"
|
||||
:setup="setup"
|
||||
:testing="testing"
|
||||
:saving="saving"
|
||||
@edit="openEdit"
|
||||
@remove="removeSelected"
|
||||
@remove="askRemove"
|
||||
@select="selectMember"
|
||||
@test="test"
|
||||
@save="saveSetup"
|
||||
@@ -271,6 +304,14 @@ async function removeSelected() {
|
||||
@close="formOpen = false"
|
||||
@submit="submitForm"
|
||||
/>
|
||||
|
||||
<ConfirmDialog
|
||||
:open="removing !== null"
|
||||
:message="removing ? `删除入站「${removing.name}」?端口 ${removing.port} 将停止监听。` : ''"
|
||||
action="删除"
|
||||
@close="removing = null"
|
||||
@confirm="removeConfirmed"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user