diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py
index 5b29fca0d..15fb53402 100644
--- a/astrbot/dashboard/routes/config.py
+++ b/astrbot/dashboard/routes/config.py
@@ -149,9 +149,10 @@ class ConfigRoute(Route):
plugin_name = request.args.get("plugin_name", "unknown")
try:
await self._save_plugin_configs(post_configs, plugin_name)
+ await self.core_lifecycle.plugin_manager.reload(plugin_name)
return (
Response()
- .ok(None, f"保存插件 {plugin_name} 成功~ 机器人正在重载配置。")
+ .ok(None, f"保存插件 {plugin_name} 成功~ 机器人正在热重载插件。")
.__dict__
)
except Exception as e:
@@ -315,6 +316,5 @@ class ConfigRoute(Route):
try:
save_config(post_configs, md.config)
- self.core_lifecycle.restart()
except Exception as e:
raise e
diff --git a/dashboard/src/views/ExtensionPage.vue b/dashboard/src/views/ExtensionPage.vue
index 3250cf5d0..145b32da9 100644
--- a/dashboard/src/views/ExtensionPage.vue
+++ b/dashboard/src/views/ExtensionPage.vue
@@ -6,6 +6,232 @@ import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue';
import axios from 'axios';
import { useCommonStore } from '@/stores/common';
+// 将所有状态和方法迁移到 setup 语法中
+import { ref, computed, onMounted, reactive } from 'vue';
+
+const commonStore = useCommonStore();
+const extension_data = reactive({
+ data: [],
+ message: ""
+});
+const showReserved = ref(false);
+const snack_message = ref("");
+const snack_show = ref(false);
+const snack_success = ref("success");
+const configDialog = ref(false);
+const extension_config = reactive({
+ metadata: {},
+ config: {}
+});
+const pluginMarketData = ref([]);
+const loadingDialog = reactive({
+ show: false,
+ title: "加载中...",
+ statusCode: 0, // 0: loading, 1: success, 2: error,
+ result: ""
+});
+const showPluginInfoDialog = ref(false);
+const selectedPlugin = ref({});
+const curr_namespace = ref("");
+const wfr = ref(null);
+
+const plugin_handler_info_headers = [
+ { title: '行为类型', key: 'event_type_h' },
+ { title: '描述', key: 'desc', maxWidth: '250px' },
+ { title: '具体类型', key: 'type' },
+ { title: '触发方式', key: 'cmd' },
+];
+
+const filteredExtensions = computed(() => {
+ if (showReserved.value) {
+ return extension_data.data;
+ }
+ return extension_data.data.filter(ext => !ext.reserved);
+});
+
+// 方法
+const toggleShowReserved = () => {
+ showReserved.value = !showReserved.value;
+};
+
+const toast = (message, success) => {
+ snack_message.value = message;
+ snack_show.value = true;
+ snack_success.value = success;
+};
+
+const resetLoadingDialog = () => {
+ loadingDialog.show = false;
+ loadingDialog.title = "加载中...";
+ loadingDialog.statusCode = 0;
+ loadingDialog.result = "";
+};
+
+const onLoadingDialogResult = (statusCode, result, timeToClose = 2000) => {
+ loadingDialog.statusCode = statusCode;
+ loadingDialog.result = result;
+ if (timeToClose === -1) return;
+ setTimeout(resetLoadingDialog, timeToClose);
+};
+
+const getExtensions = async () => {
+ try {
+ const res = await axios.get('/api/plugin/get');
+ Object.assign(extension_data, res.data);
+ checkUpdate();
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+const checkUpdate = () => {
+ const onlinePluginsMap = new Map();
+ const onlinePluginsNameMap = new Map();
+
+ pluginMarketData.value.forEach(plugin => {
+ if (plugin.repo) {
+ onlinePluginsMap.set(plugin.repo.toLowerCase(), plugin);
+ }
+ onlinePluginsNameMap.set(plugin.name, plugin);
+ });
+
+ extension_data.data.forEach(extension => {
+ const repoKey = extension.repo?.toLowerCase();
+ const onlinePlugin = repoKey ? onlinePluginsMap.get(repoKey) : null;
+ const onlinePluginByName = onlinePluginsNameMap.get(extension.name);
+ const matchedPlugin = onlinePlugin || onlinePluginByName;
+
+ if (matchedPlugin) {
+ extension.online_version = matchedPlugin.version;
+ extension.has_update = extension.version !== matchedPlugin.version &&
+ matchedPlugin.version !== "未知";
+ } else {
+ extension.has_update = false;
+ }
+ });
+};
+
+const uninstallExtension = async (extension_name) => {
+ toast("正在卸载" + extension_name, "primary");
+ try {
+ const res = await axios.post('/api/plugin/uninstall', { name: extension_name });
+ if (res.data.status === "error") {
+ toast(res.data.message, "error");
+ return;
+ }
+ Object.assign(extension_data, res.data);
+ toast(res.data.message, "success");
+ getExtensions();
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+const updateExtension = async (extension_name) => {
+ loadingDialog.show = true;
+ try {
+ const res = await axios.post('/api/plugin/update', {
+ name: extension_name,
+ proxy: localStorage.getItem('selectedGitHubProxy') || ""
+ });
+
+ if (res.data.status === "error") {
+ onLoadingDialogResult(2, res.data.message, -1);
+ return;
+ }
+
+ Object.assign(extension_data, res.data);
+ onLoadingDialogResult(1, res.data.message);
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+const pluginOn = async (extension) => {
+ try {
+ const res = await axios.post('/api/plugin/on', { name: extension.name });
+ if (res.data.status === "error") {
+ toast(res.data.message, "error");
+ return;
+ }
+ toast(res.data.message, "success");
+ getExtensions();
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+const pluginOff = async (extension) => {
+ try {
+ const res = await axios.post('/api/plugin/off', { name: extension.name });
+ if (res.data.status === "error") {
+ toast(res.data.message, "error");
+ return;
+ }
+ toast(res.data.message, "success");
+ getExtensions();
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+const openExtensionConfig = async (extension_name) => {
+ curr_namespace.value = extension_name;
+ configDialog.value = true;
+ try {
+ const res = await axios.get('/api/config/get?plugin_name=' + extension_name);
+ extension_config.metadata = res.data.data.metadata;
+ extension_config.config = res.data.data.config;
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+const updateConfig = async () => {
+ try {
+ const res = await axios.post('/api/config/plugin/update?plugin_name=' + curr_namespace.value, extension_config.config);
+ if (res.data.status === "ok") {
+ toast(res.data.message, "success");
+ } else {
+ toast(res.data.message, "error");
+ }
+ configDialog.value = false;
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+const showPluginInfo = (plugin) => {
+ selectedPlugin.value = plugin;
+ showPluginInfoDialog.value = true;
+};
+
+const reloadPlugin = async (plugin_name) => {
+ try {
+ const res = await axios.post('/api/plugin/reload', { name: plugin_name });
+ if (res.data.status === "error") {
+ toast(res.data.message, "error");
+ return;
+ }
+ toast("重载成功", "success");
+ getExtensions();
+ } catch (err) {
+ toast(err, "error");
+ }
+};
+
+// 生命周期
+onMounted(async () => {
+ await getExtensions();
+
+ try {
+ const data = await commonStore.getPluginCollections();
+ pluginMarketData.value = data;
+ checkUpdate();
+ } catch (err) {
+ console.error("获取插件市场数据失败:", err);
+ }
+});
@@ -14,145 +240,120 @@ import { useCommonStore } from '@/stores/common';
{{ extension.desc }} 这个插件没有配置 这个插件没有配置 {{ column.title }}🧩 已安装的插件
-
-
+
详情请检查控制台
日志
- 日志
+