修复:将页面异步副作用绑定到当前归属

This commit is contained in:
cieanyau committed 2026-11-07 04:00:10 +08:00
1 parent b3a66d97c8
commit eca3aa8fe5
6 files changed
+623 -90

No files matched your search

+26 -12
View File
@@ -9,6 +9,7 @@ import {
reactive,
ref,
useId,
watch,
} from "vue";
import Button from "openvue/button";
import Column from "openvue/column";
@@ -19,11 +20,12 @@ import Message from "openvue/message";
import Paginator from "openvue/paginator";
import Textarea from "openvue/textarea";
import ListEmpty from "~/components/ListEmpty.vue";
import { useApi } from "~/lib/auth.js";
import { useApi, useAuth } from "~/lib/auth.js";
import { n as loadCompleteList } from "~/lib/complete-list.js";
import "~/assets/styles/ssh-keys.WWTOf2zG.css";
const api = useApi();
const session = useAuth().prepareLogin();
const fieldId = useId();
const draftSearch = ref("");
const appliedSearch = ref("");
@@ -43,6 +45,16 @@ const copiedKeyId = ref(null);
let copyResetTimer;
let requestGeneration = 0;
let disposed = false;
let editorGeneration = 0;
const isCurrent = () => !disposed && session.isCurrent();
watch(
editorVisible,
() => {
editorGeneration++;
saving.value = false;
},
{ flush: "sync" },
);
const firstRecord = computed(() => (currentPage.value - 1) * pageSize.value);
const visibleRows = computed(() =>
@@ -53,7 +65,7 @@ const visibleRows = computed(() =>
// 仅允许当前加载代次且组件尚未卸载时修改列表反馈。
function isCurrentLoad(generation) {
return !disposed && generation === requestGeneration;
return isCurrent() && generation === requestGeneration;
}
// 删除或搜索后夹紧页码;返回是否变化,供服务端分页决定是否重读修正后的页。
@@ -67,7 +79,7 @@ function clampPage() {
// 非搜索模式请求当前服务端页,删除末页最后一条时先修正页码再重读。
async function loadKeys() {
if (disposed) return;
if (!isCurrent()) return;
if (appliedSearch.value) return loadSearchResults();
const generation = ++requestGeneration;
searchLoading.value = false;
@@ -174,7 +186,7 @@ function openEditDialog(key) {
// 校验名称非空、字符数上限和公钥非空;按编辑对象选择创建/更新,成功后刷新当前模式列表。
async function saveKey() {
if (saving.value || disposed) return;
if (saving.value || !isCurrent()) return;
actionError.value = "";
if (!keyForm.name.trim()) {
actionError.value = "请输入密钥名称";
@@ -189,6 +201,8 @@ async function saveKey() {
return;
}
saving.value = true;
const generation = editorGeneration;
const ownsDraft = () => isCurrent() && generation === editorGeneration;
try {
if (editingKey.value) {
await api.put("/ssh_key/" + editingKey.value.id, {
@@ -198,25 +212,25 @@ async function saveKey() {
} else {
await api.post("/ssh_key", { ...keyForm });
}
if (disposed) return;
editorVisible.value = false;
if (!isCurrent()) return;
if (ownsDraft()) editorVisible.value = false;
await loadKeys();
} catch (error) {
if (!disposed)
if (ownsDraft())
actionError.value = error instanceof Error ? error.message : "保存失败";
} finally {
if (!disposed) saving.value = false;
if (ownsDraft()) saving.value = false;
}
}
// 删除后复用加载逻辑;末页恢复由 loadKeys/clampPage 统一处理。
async function deleteKey(key) {
if (disposed) return;
if (!isCurrent()) return;
try {
await api.del("/ssh_key/" + key.id);
await loadKeys();
if (isCurrent()) await loadKeys();
} catch (error) {
if (!disposed)
if (isCurrent())
actionError.value = error instanceof Error ? error.message : "删除失败";
}
}
@@ -225,7 +239,7 @@ async function deleteKey(key) {
async function copyPublicKey(key) {
try {
await navigator.clipboard.writeText(key.public_key);
if (disposed) return;
if (!isCurrent()) return;
clearTimeout(copyResetTimer);
copiedKeyId.value = key.id;
copyResetTimer = setTimeout(() => (copiedKeyId.value = null), 2000);