Files
mihomo-patchbay/web/components/HistoryDialog.vue
T
im0o f761d13579 反馈改右上角轻提示;侧栏加撤回重做等配置动作
设置页的横幅换成 toast(右滑入、几秒后上移淡出,web/toast.ts 单例):
动作结果看一眼就行,不值得在页面里占一块常驻位置。表单校验类的错误
仍留在各自表单里。

撤回栈就是备份链:撤回把最新备份应用回去并消费掉它,撤回前的状态进
重做栈(.patchbay-redo,同样留 10 份);重做反向;新的正常保存清空
重做栈,不留分叉历史。这两条路复用 applyConfig 的保护链路,用
restoreFrom 参数跳过自建备份——否则备份链会被撤回本身污染,撤两次
等于原地打转。侧栏左下角一排图标:撤回、重做、历史记录(备份列表
弹窗)、更新配置(POST /api/kernel/reload,给 SSH 手改后用)、重启
内核;收拢时容器变窄自动折成一列。

设置页顺手修三处:GEO 行的编辑按钮在输入框开着时再点会闪(blur 关了
click 又开),mousedown.prevent 保焦点、click 显式开关;上传按钮改
「选择或拖放文件」,投放区拖入高亮;恢复策略挪到恢复行下面。
2026-08-12 01:59:22 +08:00

182 lines
4.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* 备份链(历史记录)的弹窗:列出每次改动前留下的备份,点还原直接跳回去。
* 还原走服务端同一条保护链路,当前状态会先自动备份,所以不需要再套一层确认。
*/
import { ref, watch } from 'vue'
import { backupTime } from '@shared/backups.ts'
import { api } from '../api'
import { toast, toastError } from '../toast'
import IconClose from '~icons/ri/close-line'
const props = defineProps<{ open: boolean }>()
/** restored:还原成功,外壳该重读配置了。 */
const emit = defineEmits<{ close: []; restored: [] }>()
const dialog = ref<HTMLDialogElement | null>(null)
const backups = ref<string[] | null>(null)
const busy = ref(false)
watch(
() => props.open,
(open) => {
if (open) {
dialog.value?.showModal()
backups.value = null
void api
.listBackups()
.then((r) => (backups.value = r.backups))
.catch(() => (backups.value = []))
} else {
dialog.value?.close()
}
},
)
async function restore(name: string) {
if (busy.value) return
busy.value = true
try {
const result = await api.restoreBackup(name)
const time = backupTime(name) ?? name
toast(result.reloaded ? `已还原到 ${time}` : `已还原到 ${time}(${result.reloadMessage})`)
emit('restored')
emit('close')
} catch (error) {
toastError(error)
} finally {
busy.value = false
}
}
/** 点在遮罩上就关闭。内容区的点击 target 是里面的元素,到不了 dialog 本身。 */
function onBackdrop(event: MouseEvent) {
if (event.target === dialog.value) emit('close')
}
</script>
<template>
<dialog
ref="dialog"
class="dlg"
@click="onBackdrop"
@close="emit('close')"
@cancel.prevent="emit('close')"
>
<div class="body">
<header class="head">
<h2>历史记录</h2>
<button type="button" class="x" title="关闭" @click="emit('close')"><IconClose /></button>
</header>
<p v-if="backups === null" class="faint">载入中</p>
<p v-else-if="backups.length === 0" class="faint">
没有备份。每次保存都会把改动前的状态留在这里,最多 10 份。
</p>
<ul v-else class="list">
<li v-for="name in backups" :key="name" class="item" :title="name">
<span class="mono time">{{ backupTime(name) ?? name }}</span>
<button type="button" class="btn" :disabled="busy" @click="restore(name)">还原</button>
</li>
</ul>
</div>
</dialog>
</template>
<style scoped>
.dlg {
width: min(420px, 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);
}
.body {
padding: var(--s-5);
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--s-3);
}
.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);
}
.faint {
color: var(--c-text-faint);
font-size: var(--t-sm);
}
.list {
list-style: none;
margin: 0;
padding: 0;
max-height: 50vh;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--c-border-strong) transparent;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--s-4);
padding: var(--s-2) 0;
}
.item + .item {
border-top: 1px solid var(--c-border);
}
.time {
font-size: var(--t-sm);
}
.btn {
padding: var(--s-1) var(--s-3);
border: 1px solid var(--c-border-strong);
border-radius: var(--r-sm);
background: var(--c-surface);
font-size: var(--t-sm);
cursor: pointer;
}
.btn:hover:not(:disabled) {
border-color: var(--c-accent);
color: var(--c-accent-text);
}
.btn:disabled {
color: var(--c-text-faint);
cursor: not-allowed;
}
</style>