From e6cf35b6879c3cfea96b8bbc332a8cca0a60102f Mon Sep 17 00:00:00 2001 From: M1LKTEA <3494199620@qq.com> Date: Tue, 12 May 2026 09:49:16 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat=EF=BC=9A=E5=AF=BC=E5=85=A5=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E7=9A=84=E8=A7=A3=E6=9E=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/package.json | 1 + dashboard/pnpm-lock.yaml | 26 +++- .../extension/PluginImportDialog.vue | 118 ++++++++++++++++++ .../views/extension/InstalledPluginsTab.vue | 74 +++++++++++ 4 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 dashboard/src/components/extension/PluginImportDialog.vue diff --git a/dashboard/package.json b/dashboard/package.json index 7770debea4..e2bb540b44 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -28,6 +28,7 @@ "js-md5": "^0.8.3", "katex": "^0.16.27", "lodash": "4.17.23", + "lz-string": "^1.5.0", "markdown-it": "^14.1.1", "markstream-vue": "^0.0.6", "mermaid": "^11.12.2", diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index c605692713..c9f49ec7ef 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -5,8 +5,8 @@ settings: excludeLinksFromLockfile: false overrides: - immutable: "4.3.8" - lodash-es: "4.17.23" + immutable: 4.3.8 + lodash-es: 4.17.23 importers: @@ -51,6 +51,9 @@ importers: lodash: specifier: 4.17.23 version: 4.17.23 + lz-string: + specifier: ^1.5.0 + version: 1.5.0 markdown-it: specifier: ^14.1.1 version: 14.1.1 @@ -537,66 +540,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -2067,6 +2083,10 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -5050,6 +5070,8 @@ snapshots: lodash@4.17.23: {} + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 diff --git a/dashboard/src/components/extension/PluginImportDialog.vue b/dashboard/src/components/extension/PluginImportDialog.vue new file mode 100644 index 0000000000..877bf80442 --- /dev/null +++ b/dashboard/src/components/extension/PluginImportDialog.vue @@ -0,0 +1,118 @@ + + + diff --git a/dashboard/src/views/extension/InstalledPluginsTab.vue b/dashboard/src/views/extension/InstalledPluginsTab.vue index cbea3281c7..abb84ab139 100644 --- a/dashboard/src/views/extension/InstalledPluginsTab.vue +++ b/dashboard/src/views/extension/InstalledPluginsTab.vue @@ -1,6 +1,8 @@ - {{ plugin.name || "(未命名)" }} + {{ plugin.name || tm("exportImport.unnamed") }} v{{ plugin.version || "?" }} - {{ plugin.repo }} + + {{ plugin.repo }} + { :loading="installing" @click="importSelected" > - 导入选中 ({{ selectedCount }}) + {{ tm("exportImport.importSelected") }} ({{ selectedCount }}) { :loading="installing" @click="importAll" > - 全部导入 + {{ tm("exportImport.importAll") }} @@ -324,4 +345,14 @@ const statusIcon = (idx) => { transform: rotate(360deg); } } + +.repo-link { + color: rgb(var(--v-theme-primary)); + text-decoration: none; + word-break: break-all; +} + +.repo-link:hover { + text-decoration: underline; +} diff --git a/dashboard/src/i18n/locales/en-US/features/extension.json b/dashboard/src/i18n/locales/en-US/features/extension.json index ae29f22357..3944444a4a 100644 --- a/dashboard/src/i18n/locales/en-US/features/extension.json +++ b/dashboard/src/i18n/locales/en-US/features/extension.json @@ -426,5 +426,37 @@ }, "pluginChangelog": { "menuTitle": "View Changelog" + }, + "exportImport": { + "exportPlugin": "Export Plugins", + "exportFiltered": "Export All Filtered Plugins", + "exportPinned": "Export Pinned Plugins", + "exportSelected": "Pick Plugins to Export", + "importPlugin": "Import Plugins", + "pluginCode": "Plugin Code", + "pasteCode": "Paste Plugin Code", + "securityHint": "Imported plugins are downloaded from GitHub repositories. Please verify their security yourself.", + "parse": "Parse", + "cancel": "Cancel", + "export": "Export", + "unnamed": "(Unnamed)", + "importSelected": "Import Selected", + "importAll": "Import All", + "importing": "Importing", + "completed": "Completed", + "exportSummary": "{total} plugins, {selected} selected", + "importSummary": "Parsed {total} plugins, {selected} selected", + "errors": { + "nothingToExport": "Nothing to export", + "needOneSelection": "Please select at least one plugin", + "copySuccess": "Copied to clipboard", + "copyFailed": "Copy failed", + "needCode": "Please paste the plugin code first", + "parseFailedFormat": "Failed to parse plugin code, please check the format", + "parseFailedContent": "Invalid plugin code content format", + "parseFailed": "Failed to parse plugin code: {msg}", + "missingRepo": "Missing repository URL", + "unknownError": "Unknown error" + } } } diff --git a/dashboard/src/i18n/locales/zh-CN/features/extension.json b/dashboard/src/i18n/locales/zh-CN/features/extension.json index bd88ba25a9..27825969b3 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/extension.json +++ b/dashboard/src/i18n/locales/zh-CN/features/extension.json @@ -426,5 +426,37 @@ }, "pluginChangelog": { "menuTitle": "查看更新日志" + }, + "exportImport": { + "exportPlugin": "导出插件", + "exportFiltered": "导出全部筛选出的插件", + "exportPinned": "导出置顶的插件", + "exportSelected": "挑选插件导出", + "importPlugin": "导入插件", + "pluginCode": "插件码", + "pasteCode": "粘贴插件码", + "securityHint": "导入下载的插件均来自 GitHub 仓库,请自行检查插件的安全性", + "parse": "解析", + "cancel": "取消", + "export": "导出", + "unnamed": "(未命名)", + "importSelected": "导入选中", + "importAll": "全部导入", + "importing": "导入中", + "completed": "完成", + "exportSummary": "共 {total} 个插件,已选 {selected} 个", + "importSummary": "共解析到 {total} 个插件,已选 {selected} 个", + "errors": { + "nothingToExport": "没有可导出的插件", + "needOneSelection": "请至少选择一个插件", + "copySuccess": "已复制到剪贴板", + "copyFailed": "复制失败", + "needCode": "请先粘贴插件码", + "parseFailedFormat": "插件码解析失败,请检查格式", + "parseFailedContent": "插件码内容格式错误", + "parseFailed": "插件码解析失败:{msg}", + "missingRepo": "缺少仓库地址", + "unknownError": "未知错误" + } } } diff --git a/dashboard/src/views/extension/InstalledPluginsTab.vue b/dashboard/src/views/extension/InstalledPluginsTab.vue index b7b366cfd6..1609b8f5c6 100644 --- a/dashboard/src/views/extension/InstalledPluginsTab.vue +++ b/dashboard/src/views/extension/InstalledPluginsTab.vue @@ -219,7 +219,7 @@ const exportablePlugins = computed(() => const exportPlugin = (pluginList) => { if (!pluginList || pluginList.length === 0) { - toast("没有可导出的插件", "warning"); + toast(tm("exportImport.errors.nothingToExport"), "warning"); return; } showExportCode.value = true; @@ -274,7 +274,7 @@ const toggleExportSelectAll = () => { const confirmExportSelected = () => { const picked = exportablePlugins.value.filter((_, i) => exportSelected.value[i]); if (picked.length === 0) { - toast("请至少选择一个插件", "warning"); + toast(tm("exportImport.errors.needOneSelection"), "warning"); return; } showExportSelectDialog.value = false; @@ -284,10 +284,10 @@ const confirmExportSelected = () => { const copyExportCode = async () => { try { await navigator.clipboard.writeText(exportCode.value); - toast("已复制到剪贴板", "success"); + toast(tm("exportImport.errors.copySuccess"), "success"); } catch (err) { - console.error("复制失败", err); - toast("复制失败", "error"); + console.error("Copy failed", err); + toast(tm("exportImport.errors.copyFailed"), "error"); } } @@ -316,18 +316,18 @@ const openImportDialog = () => { prepend-icon="mdi-export-variant" append-icon="mdi-menu-down" > - 导出插件 + {{ tm("exportImport.exportPlugin") }} - 导出全部筛选出的插件 + {{ tm("exportImport.exportFiltered") }} - 导出置顶的插件 + {{ tm("exportImport.exportPinned") }} - 挑选插件导出 + {{ tm("exportImport.exportSelected") }} @@ -338,7 +338,7 @@ const openImportDialog = () => { prepend-icon="mdi-import" @click="openImportDialog" > - 导入插件 + {{ tm("exportImport.importPlugin") }} { mdi-code-braces - 插件码 + {{ tm("exportImport.pluginCode") }} @@ -389,7 +389,7 @@ const openImportDialog = () => { mdi-cursor-default-click-outline - 挑选插件导出 + {{ tm("exportImport.exportSelected") }} @@ -404,7 +404,7 @@ const openImportDialog = () => { @update:model-value="toggleExportSelectAll" /> - 共 {{ exportablePlugins.length }} 个插件,已选 {{ selectedExportCount }} 个 + {{ tm("exportImport.exportSummary", { total: exportablePlugins.length, selected: selectedExportCount }) }} @@ -436,13 +436,21 @@ const openImportDialog = () => { - {{ plugin.name || "(未命名)" }} + {{ plugin.name || tm("exportImport.unnamed") }} v{{ plugin.version || "?" }} - {{ plugin.repo }} + + {{ plugin.repo }} + @@ -450,7 +458,7 @@ const openImportDialog = () => { - 取消 + {{ tm("exportImport.cancel") }} { :disabled="selectedExportCount === 0" @click="confirmExportSelected" > - 导出 ({{ selectedExportCount }}) + {{ tm("exportImport.export") }} ({{ selectedExportCount }}) @@ -633,6 +641,16 @@ const openImportDialog = () => {