From 1120055eedb0f5fd9025e4cb3929aaaa37f7247f Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sat, 18 Apr 2026 03:01:16 +0800 Subject: [PATCH 1/6] feat(plugin): support local plugin sources --- src/components/cards/PluginAppCard.vue | 3 +++ src/views/plugin/PluginCardListView.vue | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/cards/PluginAppCard.vue b/src/components/cards/PluginAppCard.vue index 37789559..b4643f9e 100644 --- a/src/components/cards/PluginAppCard.vue +++ b/src/components/cards/PluginAppCard.vue @@ -118,6 +118,9 @@ const iconPath: Ref = computed(() => { function visitPluginPage() { // 将raw.githubusercontent.com转换为项目地址 let repoUrl = props.plugin?.repo_url + if (repoUrl?.startsWith('local://')) { + repoUrl = props.plugin?.author_url + } if (repoUrl) { if (repoUrl.includes('raw.githubusercontent.com')) { if (!repoUrl.endsWith('/')) repoUrl += '/' diff --git a/src/views/plugin/PluginCardListView.vue b/src/views/plugin/PluginCardListView.vue index 8d05f9eb..8007150f 100644 --- a/src/views/plugin/PluginCardListView.vue +++ b/src/views/plugin/PluginCardListView.vue @@ -610,15 +610,18 @@ async function saveFolderPluginOrder() { // 初始化过滤选项 function initOptions(item: Plugin) { + const LOCAL_REPO_LABEL = '本地' const optionValue = (options: Array, value: string | undefined) => { - value && !options.includes(value) && options.push(value) + if (!value || options.includes(value)) return + if (value === LOCAL_REPO_LABEL) options.unshift(value) + else options.push(value) } const optionMutipleValue = (options: Array, value: string | undefined) => { value && value.split(',').forEach(v => !options.includes(v) && options.push(v)) } optionValue(authorFilterOptions.value, item.plugin_author) optionMutipleValue(labelFilterOptions.value, item.plugin_label) - optionValue(repoFilterOptions.value, handleRepoUrl(item.repo_url)) + optionValue(repoFilterOptions.value, handleRepoUrl(item)) } // 关闭插件市场窗口 @@ -650,7 +653,7 @@ async function installPlugin(item: Plugin) { enabledFilter.value = false installedFilter.value = null // 刷新 - refreshData() + await refreshData() } else { $toast.error(t('plugin.installFailed', { name: item?.plugin_name, message: result.message })) } @@ -750,6 +753,7 @@ async function fetchUninstalledPlugins(force: boolean = false) { // 排除已安装且有更新的,上面的问题在于"本地存在未安装的旧版本插件且云端有更新时"不会在插件市场展示 marketList.value = uninstalledList.value.filter(item => !(item.has_update && item.installed)) // 初始化过滤选项 + repoFilterOptions.value = [] marketList.value.forEach(initOptions) // 设置APP市场加载完成 isAppMarketLoaded.value = true @@ -770,7 +774,7 @@ async function getPluginStatistics() { // 加载所有数据 async function refreshData() { await fetchInstalledPlugins() - fetchUninstalledPlugins() + await fetchUninstalledPlugins() // 重新加载文件夹配置,确保分身插件能正确显示在文件夹中 await loadPluginFolders() } @@ -794,7 +798,7 @@ watch([marketList, filterForm, activeSort], () => { filterText(filterForm.name, `${value.plugin_name} ${value.plugin_desc}`) && match(filterForm.author, value.plugin_author) && matchMultiple(filterForm.label, value.plugin_label) && - match(filterForm.repo, handleRepoUrl(value.repo_url)) + match(filterForm.repo, handleRepoUrl(value)) ) { sortedUninstalledList.value.push(value) } @@ -825,9 +829,9 @@ function pluginLabels(label: string | undefined) { } // 新安装了插件 -function pluginInstalled() { +async function pluginInstalled() { pluginDialogClose() - refreshData() + await refreshData() } // 插件市场设置完成 @@ -851,8 +855,11 @@ async function refreshMarket() { } // 处理掉github地址的前缀 -function handleRepoUrl(url: string | undefined) { +function handleRepoUrl(item: Plugin | string | undefined) { + const url = typeof item === 'string' ? item : item?.repo_url + if (typeof item !== 'string' && item?.is_local) return '本地' if (!url) return '' + if (url.startsWith('local://')) return '本地' return url.replace('https://github.com/', '').replace('https://raw.githubusercontent.com/', '') } From 48c12b765d56bac07ee0d9f450bd84ec1a81ac06 Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sat, 18 Apr 2026 03:11:55 +0800 Subject: [PATCH 2/6] feat(setting): expose local plugin paths --- src/locales/en-US.ts | 3 +++ src/locales/zh-CN.ts | 2 ++ src/locales/zh-TW.ts | 2 ++ src/views/setting/AccountSettingSystem.vue | 10 ++++++++++ 4 files changed, 17 insertions(+) diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 28600122..994ef241 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -1467,6 +1467,9 @@ export default { logFileFormatHint: 'Set the output format of log files to customize the displayed content of logs', pluginAutoReload: 'Plugin Hot Reload', pluginAutoReloadHint: 'Automatically reload after modifying plugin files, used when developing plugins', + pluginLocalPaths: 'Local Plugin Paths', + pluginLocalPathsHint: + 'Local plugin repository directories. Separate multiple directories with commas. Relative and absolute paths are supported.', encodingDetectionPerformanceMode: 'Encoding Detection Performance Mode', encodingDetectionPerformanceModeHint: 'Prioritize detection efficiency, but may reduce encoding detection accuracy', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 64b52ac6..8b66e4c1 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1459,6 +1459,8 @@ export default { logFileFormatHint: '设置日志文件的输出格式,用于自定义日志的显示内容', pluginAutoReload: '插件热加载', pluginAutoReloadHint: '修改插件文件后自动重新加载,开发插件时使用', + pluginLocalPaths: '本地插件路径', + pluginLocalPathsHint: '本地插件仓库目录,多个目录用英文逗号分隔,支持相对路径和绝对路径', encodingDetectionPerformanceMode: '编码探测性能模式', encodingDetectionPerformanceModeHint: '优先提升探测效率,但可能降低编码探测的准确性', transferThreads: '文件整理线程数', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 41d35d64..43c2083c 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -1460,6 +1460,8 @@ export default { logFileFormatHint: '設置日誌文件的輸出格式,用於自定義日誌的顯示內容', pluginAutoReload: '插件熱加載', pluginAutoReloadHint: '修改插件文件後自動重新加載,開發插件時使用', + pluginLocalPaths: '本地插件路徑', + pluginLocalPathsHint: '本地插件倉庫目錄,多個目錄用英文逗號分隔,支持相對路徑和絕對路徑', encodingDetectionPerformanceMode: '編碼探測性能模式', encodingDetectionPerformanceModeHint: '優先提升探測效率,但可能降低編碼探測的準確性', transferThreads: '文件整理線程數', diff --git a/src/views/setting/AccountSettingSystem.vue b/src/views/setting/AccountSettingSystem.vue index 7bc560f7..d02d1a0b 100644 --- a/src/views/setting/AccountSettingSystem.vue +++ b/src/views/setting/AccountSettingSystem.vue @@ -84,6 +84,7 @@ const SystemSettings = ref({ LOG_FILE_FORMAT: '【%(levelname)s】%(asctime)s - %(message)s', // 实验室 PLUGIN_AUTO_RELOAD: false, + PLUGIN_LOCAL_PATHS: '', ENCODING_DETECTION_PERFORMANCE_MODE: true, TRANSFER_THREADS: 1, }, @@ -1458,6 +1459,15 @@ onDeactivated(() => { persistent-hint /> + + + Date: Sun, 19 Apr 2026 02:54:09 +0800 Subject: [PATCH 3/6] fix(plugin): local source label and detection --- src/components/cards/PluginAppCard.vue | 2 +- src/locales/en-US.ts | 1 + src/locales/zh-CN.ts | 1 + src/locales/zh-TW.ts | 1 + src/views/plugin/PluginCardListView.vue | 10 ++++++---- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/cards/PluginAppCard.vue b/src/components/cards/PluginAppCard.vue index b4643f9e..38819522 100644 --- a/src/components/cards/PluginAppCard.vue +++ b/src/components/cards/PluginAppCard.vue @@ -118,7 +118,7 @@ const iconPath: Ref = computed(() => { function visitPluginPage() { // 将raw.githubusercontent.com转换为项目地址 let repoUrl = props.plugin?.repo_url - if (repoUrl?.startsWith('local://')) { + if (props.plugin?.is_local || repoUrl?.startsWith('local://')) { repoUrl = props.plugin?.author_url } if (repoUrl) { diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 994ef241..486c7003 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -2601,6 +2601,7 @@ export default { settings: 'Settings', projectHome: 'Project Home', updateHistory: 'Update History', + local: 'Local', installToLocal: 'Install to Local', totalDownloads: 'Total {count} downloads', viewData: 'View Data', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 8b66e4c1..9e5bbfbc 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -2571,6 +2571,7 @@ export default { settings: '设置', projectHome: '项目主页', updateHistory: '更新说明', + local: '本地', installToLocal: '安装到本地', totalDownloads: '共 {count} 次下载', viewData: '查看数据', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 43c2083c..8de4f0bd 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -2572,6 +2572,7 @@ export default { settings: '設置', projectHome: '項目主頁', updateHistory: '更新說明', + local: '本地', installToLocal: '安裝到本地', totalDownloads: '共 {count} 次下載', viewData: '查看數據', diff --git a/src/views/plugin/PluginCardListView.vue b/src/views/plugin/PluginCardListView.vue index 8007150f..80e73d00 100644 --- a/src/views/plugin/PluginCardListView.vue +++ b/src/views/plugin/PluginCardListView.vue @@ -34,6 +34,9 @@ const activeTab = ref('installed') // 获取插件标签页 const pluginTabs = computed(() => getPluginTabs(t)) +// 本地插件来源显示名称 +const localRepoLabel = computed(() => t('plugin.local')) + // 使用动态标签页 const { registerHeaderTab } = useDynamicHeaderTab() @@ -610,10 +613,9 @@ async function saveFolderPluginOrder() { // 初始化过滤选项 function initOptions(item: Plugin) { - const LOCAL_REPO_LABEL = '本地' const optionValue = (options: Array, value: string | undefined) => { if (!value || options.includes(value)) return - if (value === LOCAL_REPO_LABEL) options.unshift(value) + if (value === localRepoLabel.value) options.unshift(value) else options.push(value) } const optionMutipleValue = (options: Array, value: string | undefined) => { @@ -857,9 +859,9 @@ async function refreshMarket() { // 处理掉github地址的前缀 function handleRepoUrl(item: Plugin | string | undefined) { const url = typeof item === 'string' ? item : item?.repo_url - if (typeof item !== 'string' && item?.is_local) return '本地' + if (typeof item !== 'string' && item?.is_local) return localRepoLabel.value if (!url) return '' - if (url.startsWith('local://')) return '本地' + if (url.startsWith('local://')) return localRepoLabel.value return url.replace('https://github.com/', '').replace('https://raw.githubusercontent.com/', '') } From 7b20a7b7754ed55b500d229d9889adee83acde8f Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sun, 19 Apr 2026 03:03:22 +0800 Subject: [PATCH 4/6] refactor(setting): rename local repo paths setting --- src/locales/en-US.ts | 4 ++-- src/locales/zh-CN.ts | 4 ++-- src/locales/zh-TW.ts | 4 ++-- src/views/setting/AccountSettingSystem.vue | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index 486c7003..3bbcf732 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -1467,8 +1467,8 @@ export default { logFileFormatHint: 'Set the output format of log files to customize the displayed content of logs', pluginAutoReload: 'Plugin Hot Reload', pluginAutoReloadHint: 'Automatically reload after modifying plugin files, used when developing plugins', - pluginLocalPaths: 'Local Plugin Paths', - pluginLocalPathsHint: + pluginLocalRepoPaths: 'Local Plugin Repository Paths', + pluginLocalRepoPathsHint: 'Local plugin repository directories. Separate multiple directories with commas. Relative and absolute paths are supported.', encodingDetectionPerformanceMode: 'Encoding Detection Performance Mode', encodingDetectionPerformanceModeHint: diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index 9e5bbfbc..cb314f56 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -1459,8 +1459,8 @@ export default { logFileFormatHint: '设置日志文件的输出格式,用于自定义日志的显示内容', pluginAutoReload: '插件热加载', pluginAutoReloadHint: '修改插件文件后自动重新加载,开发插件时使用', - pluginLocalPaths: '本地插件路径', - pluginLocalPathsHint: '本地插件仓库目录,多个目录用英文逗号分隔,支持相对路径和绝对路径', + pluginLocalRepoPaths: '本地插件仓库路径', + pluginLocalRepoPathsHint: '本地插件仓库目录,多个目录用英文逗号分隔,支持相对路径和绝对路径', encodingDetectionPerformanceMode: '编码探测性能模式', encodingDetectionPerformanceModeHint: '优先提升探测效率,但可能降低编码探测的准确性', transferThreads: '文件整理线程数', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 8de4f0bd..282d6547 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -1460,8 +1460,8 @@ export default { logFileFormatHint: '設置日誌文件的輸出格式,用於自定義日誌的顯示內容', pluginAutoReload: '插件熱加載', pluginAutoReloadHint: '修改插件文件後自動重新加載,開發插件時使用', - pluginLocalPaths: '本地插件路徑', - pluginLocalPathsHint: '本地插件倉庫目錄,多個目錄用英文逗號分隔,支持相對路徑和絕對路徑', + pluginLocalRepoPaths: '本地插件倉庫路徑', + pluginLocalRepoPathsHint: '本地插件倉庫目錄,多個目錄用英文逗號分隔,支持相對路徑和絕對路徑', encodingDetectionPerformanceMode: '編碼探測性能模式', encodingDetectionPerformanceModeHint: '優先提升探測效率,但可能降低編碼探測的準確性', transferThreads: '文件整理線程數', diff --git a/src/views/setting/AccountSettingSystem.vue b/src/views/setting/AccountSettingSystem.vue index d02d1a0b..a240a934 100644 --- a/src/views/setting/AccountSettingSystem.vue +++ b/src/views/setting/AccountSettingSystem.vue @@ -84,7 +84,7 @@ const SystemSettings = ref({ LOG_FILE_FORMAT: '【%(levelname)s】%(asctime)s - %(message)s', // 实验室 PLUGIN_AUTO_RELOAD: false, - PLUGIN_LOCAL_PATHS: '', + PLUGIN_LOCAL_REPO_PATHS: '', ENCODING_DETECTION_PERFORMANCE_MODE: true, TRANSFER_THREADS: 1, }, @@ -1461,9 +1461,9 @@ onDeactivated(() => { From b54e144d0e00689af303f0d7763c4cb8ba836f64 Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sun, 19 Apr 2026 04:20:49 +0800 Subject: [PATCH 5/6] feat(plugin): show local repo paths in repository filter --- src/views/plugin/PluginCardListView.vue | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/views/plugin/PluginCardListView.vue b/src/views/plugin/PluginCardListView.vue index 80e73d00..1a8247d2 100644 --- a/src/views/plugin/PluginCardListView.vue +++ b/src/views/plugin/PluginCardListView.vue @@ -613,9 +613,9 @@ async function saveFolderPluginOrder() { // 初始化过滤选项 function initOptions(item: Plugin) { - const optionValue = (options: Array, value: string | undefined) => { + const optionValue = (options: Array, value: string | undefined, preferred = false) => { if (!value || options.includes(value)) return - if (value === localRepoLabel.value) options.unshift(value) + if (preferred) options.unshift(value) else options.push(value) } const optionMutipleValue = (options: Array, value: string | undefined) => { @@ -623,7 +623,7 @@ function initOptions(item: Plugin) { } optionValue(authorFilterOptions.value, item.plugin_author) optionMutipleValue(labelFilterOptions.value, item.plugin_label) - optionValue(repoFilterOptions.value, handleRepoUrl(item)) + optionValue(repoFilterOptions.value, handleRepoUrl(item), Boolean(item.is_local || item.repo_url?.startsWith('local://'))) } // 关闭插件市场窗口 @@ -856,12 +856,22 @@ async function refreshMarket() { } } +function parseLocalRepoPath(repoUrl: string | undefined) { + if (!repoUrl?.startsWith('local://')) return '' + + try { + return new URL(repoUrl).searchParams.get('path') || '' + } catch (error) { + return decodeURIComponent(repoUrl.match(/[?&]path=([^&]+)/)?.[1] || '') + } +} + // 处理掉github地址的前缀 function handleRepoUrl(item: Plugin | string | undefined) { const url = typeof item === 'string' ? item : item?.repo_url - if (typeof item !== 'string' && item?.is_local) return localRepoLabel.value if (!url) return '' - if (url.startsWith('local://')) return localRepoLabel.value + if (url.startsWith('local://')) return parseLocalRepoPath(url) || localRepoLabel.value + if (typeof item !== 'string' && item?.is_local) return parseLocalRepoPath(url) || localRepoLabel.value return url.replace('https://github.com/', '').replace('https://raw.githubusercontent.com/', '') } From 3f258b9016d845d763d4c1e1294135e13de00279 Mon Sep 17 00:00:00 2001 From: InfinityPacer Date: Sun, 19 Apr 2026 04:21:00 +0800 Subject: [PATCH 6/6] fix(plugin): resort market list when statistics load --- src/views/plugin/PluginCardListView.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/plugin/PluginCardListView.vue b/src/views/plugin/PluginCardListView.vue index 1a8247d2..cacc72dd 100644 --- a/src/views/plugin/PluginCardListView.vue +++ b/src/views/plugin/PluginCardListView.vue @@ -777,12 +777,13 @@ async function getPluginStatistics() { async function refreshData() { await fetchInstalledPlugins() await fetchUninstalledPlugins() + getPluginStatistics() // 重新加载文件夹配置,确保分身插件能正确显示在文件夹中 await loadPluginFolders() } // 对uninstalledList进行排序到sortedUninstalledList -watch([marketList, filterForm, activeSort], () => { +watch([marketList, filterForm, activeSort, PluginStatistics], () => { // 匹配过滤函数 const match = (filter: Array, value: string | undefined) => filter.length === 0 || (value && filter.includes(value)) @@ -811,7 +812,7 @@ watch([marketList, filterForm, activeSort], () => { if (!isNullOrEmptyObject(PluginStatistics.value)) { if (!activeSort.value || activeSort.value === 'count') { sortedUninstalledList.value = sortedUninstalledList.value.sort((a, b) => { - return PluginStatistics.value[b.id || '0'] - PluginStatistics.value[a.id || '0'] + return (PluginStatistics.value[b.id || '0'] ?? 0) - (PluginStatistics.value[a.id || '0'] ?? 0) }) } else if (activeSort.value) { sortedUninstalledList.value = sortedUninstalledList.value.sort((a: any, b: any) => { @@ -848,7 +849,7 @@ async function refreshMarket() { isMarketRefreshing.value = true try { await fetchUninstalledPlugins(true) - await getPluginStatistics() + getPluginStatistics() } catch (error) { console.error(error) } finally { @@ -905,7 +906,6 @@ onMounted(async () => { await loadPluginOrderConfig() await loadPluginFolders() // 加载文件夹配置 await refreshData() - getPluginStatistics() if (activeTab.value != 'market' && pluginId.value) { // 找到这个插件 const plugin = dataList.value.find(item => item.id === pluginId.value)