From a0690a6afc99dcbba9861115334ffecbdbd9f863 Mon Sep 17 00:00:00 2001 From: Misaka Mikoto <117180744+railgun19457@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:48:48 +0800 Subject: [PATCH] feat: support options to delete plugins config and data (#3280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - 为插件管理页面中,删除插件提供一致的二次确认(原本只有卡片视图有二次确认) - 二次确认时可选删除插件配置和持久化数据 - 添加对应的i18n支持 * ruff * 移除未使用的 const $confirm = inject('$confirm'); --- astrbot/core/star/star_manager.py | 55 ++++++- astrbot/dashboard/routes/plugin.py | 8 +- .../src/components/shared/ExtensionCard.vue | 24 ++-- .../shared/UninstallConfirmDialog.vue | 135 ++++++++++++++++++ .../locales/en-US/features/extension.json | 6 +- .../locales/en-US/messages/validation.json | 3 +- .../locales/zh-CN/features/extension.json | 6 +- .../locales/zh-CN/messages/validation.json | 3 +- dashboard/src/views/ExtensionPage.vue | 54 ++++++- 9 files changed, 271 insertions(+), 23 deletions(-) create mode 100644 dashboard/src/components/shared/UninstallConfirmDialog.vue diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 7a49807f6..abdedc249 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -680,11 +680,18 @@ class PluginManager: return plugin_info - async def uninstall_plugin(self, plugin_name: str): + async def uninstall_plugin( + self, + plugin_name: str, + delete_config: bool = False, + delete_data: bool = False, + ): """卸载指定的插件。 Args: plugin_name (str): 要卸载的插件名称 + delete_config (bool): 是否删除插件配置文件,默认为 False + delete_data (bool): 是否删除插件数据,默认为 False Raises: Exception: 当插件不存在、是保留插件时,或删除插件文件夹失败时抛出异常 @@ -714,6 +721,7 @@ class PluginManager: await self._unbind_plugin(plugin_name, plugin.module_path) + # 删除插件文件夹 try: remove_dir(os.path.join(ppath, root_dir_name)) except Exception as e: @@ -721,6 +729,51 @@ class PluginManager: f"移除插件成功,但是删除插件文件夹失败: {e!s}。您可以手动删除该文件夹,位于 addons/plugins/ 下。", ) + # 删除插件配置文件 + if delete_config and root_dir_name: + config_file = os.path.join( + self.plugin_config_path, + f"{root_dir_name}_config.json", + ) + if os.path.exists(config_file): + try: + os.remove(config_file) + logger.info(f"已删除插件 {plugin_name} 的配置文件") + except Exception as e: + logger.warning(f"删除插件配置文件失败: {e!s}") + + # 删除插件持久化数据 + # 注意:需要检查两个可能的目录名(plugin_data 和 plugins_data) + # data/temp 目录可能被多个插件共享,不自动删除以防误删 + if delete_data and root_dir_name: + data_base_dir = os.path.dirname(ppath) # data/ + + # 删除 data/plugin_data 下的插件持久化数据(单数形式,新版本) + plugin_data_dir = os.path.join( + data_base_dir, "plugin_data", root_dir_name + ) + if os.path.exists(plugin_data_dir): + try: + remove_dir(plugin_data_dir) + logger.info( + f"已删除插件 {plugin_name} 的持久化数据 (plugin_data)" + ) + except Exception as e: + logger.warning(f"删除插件持久化数据失败 (plugin_data): {e!s}") + + # 删除 data/plugins_data 下的插件持久化数据(复数形式,旧版本兼容) + plugins_data_dir = os.path.join( + data_base_dir, "plugins_data", root_dir_name + ) + if os.path.exists(plugins_data_dir): + try: + remove_dir(plugins_data_dir) + logger.info( + f"已删除插件 {plugin_name} 的持久化数据 (plugins_data)" + ) + except Exception as e: + logger.warning(f"删除插件持久化数据失败 (plugins_data): {e!s}") + async def _unbind_plugin(self, plugin_name: str, plugin_module_path: str): """解绑并移除一个插件。 diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index 71b238dd7..597a245d4 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -395,9 +395,15 @@ class PluginRoute(Route): post_data = await request.json plugin_name = post_data["name"] + delete_config = post_data.get("delete_config", False) + delete_data = post_data.get("delete_data", False) try: logger.info(f"正在卸载插件 {plugin_name}") - await self.plugin_manager.uninstall_plugin(plugin_name) + await self.plugin_manager.uninstall_plugin( + plugin_name, + delete_config=delete_config, + delete_data=delete_data, + ) logger.info(f"卸载插件 {plugin_name} 成功") return Response().ok(None, "卸载成功").__dict__ except Exception as e: diff --git a/dashboard/src/components/shared/ExtensionCard.vue b/dashboard/src/components/shared/ExtensionCard.vue index 4af11cc7a..a74b40cba 100644 --- a/dashboard/src/components/shared/ExtensionCard.vue +++ b/dashboard/src/components/shared/ExtensionCard.vue @@ -2,6 +2,7 @@ import { ref, computed, inject } from 'vue'; import { useCustomizerStore } from "@/stores/customizer"; import { useModuleI18n } from '@/i18n/composables'; +import UninstallConfirmDialog from './UninstallConfirmDialog.vue'; const props = defineProps({ extension: { @@ -31,6 +32,7 @@ const emit = defineEmits([ ]); const reveal = ref(false); +const showUninstallDialog = ref(false); // 国际化 const { tm } = useModuleI18n('features/extension'); @@ -55,19 +57,11 @@ const installExtension = async () => { }; const uninstallExtension = async () => { - if (typeof $confirm !== "function") { - console.error(tm("card.errors.confirmNotRegistered")); - return; - } + showUninstallDialog.value = true; +}; - const confirmed = await $confirm({ - title: tm("dialogs.uninstall.title"), - message: tm("dialogs.uninstall.message"), - }); - - if (confirmed) { - emit("uninstall", props.extension); - } +const handleUninstallConfirm = (options: { deleteConfig: boolean; deleteData: boolean }) => { + emit("uninstall", props.extension, options); }; const toggleActivation = () => { @@ -220,6 +214,12 @@ const viewReadme = () => { + + +