Skip to content

Commit 26e68ed

Browse files
committed
refactor: extract extension helpers and simplify uninstall flow
1 parent d14aa67 commit 26e68ed

3 files changed

Lines changed: 113 additions & 114 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { reactive } from "vue";
2+
3+
export const useTimedMessage = (initialType = "success") => {
4+
const state = reactive({
5+
message: "",
6+
type: initialType,
7+
});
8+
9+
let timer = null;
10+
11+
const clearTimer = () => {
12+
if (timer) {
13+
clearTimeout(timer);
14+
timer = null;
15+
}
16+
};
17+
18+
const clearMessage = () => {
19+
state.message = "";
20+
clearTimer();
21+
};
22+
23+
const setMessage = (message, type = initialType, duration = 4000) => {
24+
state.message = message || "";
25+
state.type = type;
26+
clearTimer();
27+
if (duration > 0 && state.message) {
28+
timer = setTimeout(() => {
29+
state.message = "";
30+
}, duration);
31+
}
32+
};
33+
34+
return {
35+
state,
36+
setMessage,
37+
clearMessage,
38+
clearTimer,
39+
};
40+
};

dashboard/src/utils/errorUtils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const INVALID_ERROR_STRINGS = new Set(["[object Object]", "undefined", "null"]);
2+
3+
export const resolveErrorMessage = (err, fallbackMessage = "") => {
4+
const fromResponse = err?.response?.data?.message;
5+
const fromError = err?.message;
6+
const raw = err != null ? String(err) : "";
7+
const fromString = INVALID_ERROR_STRINGS.has(raw) ? "" : raw;
8+
9+
return fromResponse || fromError || fromString || fallbackMessage;
10+
};

dashboard/src/views/extension/useExtensionPage.js

Lines changed: 63 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,13 @@ import axios from "axios";
22
import { pinyin } from "pinyin-pro";
33
import { useCommonStore } from "@/stores/common";
44
import { useI18n, useModuleI18n } from "@/i18n/composables";
5-
import defaultPluginIcon from "@/assets/images/plugin_icon.png";
65
import { getPlatformDisplayName } from "@/utils/platformUtils";
6+
import { useTimedMessage } from "@/composables/useTimedMessage";
7+
import { resolveErrorMessage } from "@/utils/errorUtils";
78
import { ref, computed, onMounted, onUnmounted, reactive, watch } from "vue";
89
import { useRoute, useRouter } from "vue-router";
910
import { useDisplay } from "vuetify";
1011

11-
const useTimedMessage = (initialType = "success") => {
12-
const state = reactive({
13-
message: "",
14-
type: initialType,
15-
});
16-
let timer = null;
17-
18-
const clearTimer = () => {
19-
if (timer) {
20-
clearTimeout(timer);
21-
timer = null;
22-
}
23-
};
24-
25-
const setMessage = (message, type = initialType, duration = 4000) => {
26-
state.message = message || "";
27-
state.type = type;
28-
clearTimer();
29-
if (duration > 0 && state.message) {
30-
timer = setTimeout(() => {
31-
state.message = "";
32-
}, duration);
33-
}
34-
};
35-
36-
return {
37-
state,
38-
setMessage,
39-
clearTimer,
40-
};
41-
};
42-
4312
export const useExtensionPage = () => {
4413

4514

@@ -171,6 +140,7 @@ export const useExtensionPage = () => {
171140
const {
172141
state: reloadFeedback,
173142
setMessage: setReloadFeedback,
143+
clearMessage: clearReloadFeedback,
174144
clearTimer: clearReloadFeedbackTimer,
175145
} = useTimedMessage();
176146

@@ -461,14 +431,6 @@ export const useExtensionPage = () => {
461431
snack_show.value = true;
462432
snack_success.value = success;
463433
};
464-
const resolveErrorMessage = (err, fallbackMessage = "") => {
465-
return (
466-
err?.response?.data?.message ||
467-
err?.message ||
468-
String(err) ||
469-
fallbackMessage
470-
);
471-
};
472434

473435
const resetLoadingDialog = () => {
474436
loadingDialog.show = false;
@@ -549,7 +511,7 @@ export const useExtensionPage = () => {
549511

550512
const reloadFailedPlugin = async (dirName) => {
551513
// Reset stale inline feedback before each retry attempt
552-
setReloadFeedback("", "success", 0);
514+
clearReloadFeedback();
553515

554516
if (!dirName) {
555517
return;
@@ -571,47 +533,67 @@ export const useExtensionPage = () => {
571533
}
572534
};
573535

574-
const requestUninstallPlugin = (name) => {
575-
if (!name) return;
576-
uninstallTarget.value = { type: "normal", id: name };
536+
const requestUninstall = (kind, id) => {
537+
if (!id) return;
538+
uninstallTarget.value = { kind, id };
577539
showUninstallDialog.value = true;
578540
};
579541

542+
const requestUninstallPlugin = (name) => {
543+
requestUninstall("normal", name);
544+
};
545+
580546
const requestUninstallFailedPlugin = (dirName) => {
581-
if (!dirName) return;
582-
uninstallTarget.value = { type: "failed", id: dirName };
583-
showUninstallDialog.value = true;
547+
requestUninstall("failed", dirName);
584548
};
585549

586-
const uninstallFailedPlugin = async (dirName, options = {}) => {
587-
const {
588-
deleteConfig = false,
589-
deleteData = false,
590-
skipConfirm = false,
591-
} = options;
550+
const performUninstall = async (target, options = {}) => {
551+
if (!target?.id) return;
592552

593-
if (!skipConfirm) {
594-
requestUninstallFailedPlugin(dirName);
595-
return;
596-
}
553+
const { deleteConfig = false, deleteData = false } = options || {};
554+
toast(`${tm("messages.uninstalling")} ${target.id}`, "primary");
597555

598-
toast(tm("messages.uninstalling") + " " + dirName, "primary");
599556
try {
600-
const res = await axios.post("/api/plugin/uninstall-failed", {
601-
dir_name: dirName,
557+
if (target.kind === "failed") {
558+
const res = await axios.post("/api/plugin/uninstall-failed", {
559+
dir_name: target.id,
560+
delete_config: deleteConfig,
561+
delete_data: deleteData,
562+
});
563+
if (res.data.status === "error") {
564+
toast(res.data.message, "error");
565+
return;
566+
}
567+
toast(res.data.message, "success");
568+
await getExtensions();
569+
return;
570+
}
571+
572+
const res = await axios.post("/api/plugin/uninstall", {
573+
name: target.id,
602574
delete_config: deleteConfig,
603575
delete_data: deleteData,
604576
});
605577
if (res.data.status === "error") {
606578
toast(res.data.message, "error");
607579
return;
608580
}
581+
Object.assign(extension_data, res.data);
609582
toast(res.data.message, "success");
610583
await getExtensions();
611584
} catch (err) {
612585
toast(resolveErrorMessage(err), "error");
613586
}
614587
};
588+
589+
const uninstallFailedPlugin = async (dirName, options = null) => {
590+
if (!dirName) return;
591+
if (!options || typeof options !== "object") {
592+
requestUninstallFailedPlugin(dirName);
593+
return;
594+
}
595+
await performUninstall({ kind: "failed", id: dirName }, options);
596+
};
615597

616598
const checkUpdate = () => {
617599
const onlinePluginsMap = new Map();
@@ -642,69 +624,36 @@ export const useExtensionPage = () => {
642624
});
643625
};
644626

645-
const uninstallExtension = async (
646-
extension_name,
647-
optionsOrSkipConfirm = false,
648-
) => {
649-
let deleteConfig = false;
650-
let deleteData = false;
651-
let skipConfirm = false;
652-
653-
// 处理参数:可能是布尔值(旧的 skipConfirm)或对象(新的选项)
627+
const uninstallExtension = async (extensionName, optionsOrSkipConfirm = false) => {
628+
if (!extensionName) return;
629+
654630
if (typeof optionsOrSkipConfirm === "boolean") {
655-
skipConfirm = optionsOrSkipConfirm;
656-
} else if (
657-
typeof optionsOrSkipConfirm === "object" &&
658-
optionsOrSkipConfirm !== null
659-
) {
660-
deleteConfig = optionsOrSkipConfirm.deleteConfig || false;
661-
deleteData = optionsOrSkipConfirm.deleteData || false;
662-
skipConfirm = true; // 如果传递了选项对象,说明已经确认过了
663-
}
664-
665-
// 如果没有跳过确认且没有传递选项对象,显示自定义卸载对话框
666-
if (!skipConfirm) {
667-
requestUninstallPlugin(extension_name);
668-
return; // 等待对话框回调
669-
}
670-
671-
// 执行卸载
672-
toast(tm("messages.uninstalling") + " " + extension_name, "primary");
673-
try {
674-
const res = await axios.post("/api/plugin/uninstall", {
675-
name: extension_name,
676-
delete_config: deleteConfig,
677-
delete_data: deleteData,
678-
});
679-
if (res.data.status === "error") {
680-
toast(res.data.message, "error");
631+
if (!optionsOrSkipConfirm) {
632+
requestUninstallPlugin(extensionName);
681633
return;
682634
}
683-
Object.assign(extension_data, res.data);
684-
toast(res.data.message, "success");
685-
getExtensions();
686-
} catch (err) {
687-
toast(err, "error");
635+
await performUninstall({ kind: "normal", id: extensionName });
636+
return;
688637
}
638+
639+
const options =
640+
typeof optionsOrSkipConfirm === "object" && optionsOrSkipConfirm !== null
641+
? optionsOrSkipConfirm
642+
: {};
643+
await performUninstall({ kind: "normal", id: extensionName }, options);
689644
};
690645

691646
// 处理卸载确认对话框的确认事件
692-
const handleUninstallConfirm = (options) => {
647+
const handleUninstallConfirm = async (options) => {
693648
const target = uninstallTarget.value;
694649
if (!target) return;
695650

696-
if (target.type === "failed") {
697-
uninstallFailedPlugin(target.id, {
698-
deleteConfig: options?.deleteConfig || false,
699-
deleteData: options?.deleteData || false,
700-
skipConfirm: true,
701-
});
702-
} else if (target.type === "normal") {
703-
uninstallExtension(target.id, options);
651+
try {
652+
await performUninstall(target, options);
653+
} finally {
654+
uninstallTarget.value = null;
655+
showUninstallDialog.value = false;
704656
}
705-
706-
uninstallTarget.value = null;
707-
showUninstallDialog.value = false;
708657
};
709658

710659
const updateExtension = async (extension_name, forceUpdate = false) => {
@@ -908,7 +857,7 @@ export const useExtensionPage = () => {
908857

909858
const reloadPlugin = async (plugin_name) => {
910859
try {
911-
reloadFeedback.message = "";
860+
clearReloadFeedback();
912861
const res = await axios.post("/api/plugin/reload", { name: plugin_name });
913862
if (res.data.status === "error") {
914863
toast(res.data.message || tm("messages.reloadFailed"), "error");

0 commit comments

Comments
 (0)